From a373a27f5afa4b73c7117b4361493bcc5e1a8c8e Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Tue, 11 Jun 2024 17:13:45 +0000 Subject: [PATCH] merge from local --- .vscode/settings.json | 21 +- comty.js | 2 +- linebridge | 2 +- packages/app/config/defaultSettings.json | 2 +- packages/app/config/index.js | 6 +- packages/app/config/languages.json | 611 +++++++++++ packages/app/config/sidebar.json | 21 +- .../translations/{en.json => en_US.json} | 0 .../translations/{es.json => es_ES.json} | 0 packages/app/package.json | 4 - packages/app/src-tauri/tauri.conf.json | 2 +- packages/app/src/App.jsx | 6 +- .../components/BackgroundDecorator/index.jsx | 2 +- .../app/src/components/CoverEditor/index.jsx | 58 ++ .../app/src/components/CoverEditor/index.less | 45 + packages/app/src/components/Login/index.jsx | 18 - .../components/Music/PlaylistView/index.jsx | 11 +- .../MusicStudio/LyricsEditor/index.jsx | 116 +++ .../MusicStudio/LyricsEditor/index.less | 0 .../MusicStudio/LyricsTextView/index.jsx | 60 ++ .../MusicStudio/MyReleasesList/index.jsx | 55 + .../MusicStudio/MyReleasesList}/index.less | 4 +- .../MusicStudio/ReleaseEditor/index.jsx | 107 ++ .../MusicStudio/ReleaseEditor/index.less | 93 ++ .../ReleaseEditor/tabs/Advanced/index.jsx | 9 + .../tabs/BasicInformation/index.jsx | 105 ++ .../ReleaseEditor/tabs/Tracks/index.jsx | 249 +++++ .../ReleaseEditor/tabs/Tracks/index.less | 52 + .../MusicStudio/ReleaseEditor/tabs/index.jsx | 26 + .../MusicStudio/ReleaseItem/index.jsx | 51 + .../MusicStudio/ReleaseItem/index.less | 71 ++ .../MusicStudio/TrackEditor/index.jsx | 208 ++++ .../MusicStudio/TrackEditor/index.less | 57 ++ .../MusicStudio/VideoEditor/index.jsx | 14 + .../MusicStudio/VideoEditor/index.less | 0 .../app/src/components/PagePanels/index.jsx | 25 + .../PostCard/components/actions/index.jsx | 4 +- .../PostCard/components/header/index.jsx | 32 +- .../app/src/components/ReleasesList/index.jsx | 132 +++ .../src/components/ReleasesList/index.less | 52 + packages/app/src/components/TimeAgo/index.jsx | 33 + .../app/src/components/UserCard/index.jsx | 2 +- packages/app/src/cores/api/api.core.js | 4 +- packages/app/src/cores/nfc/nfc.core.js | 4 +- .../src/cores/permissions/permissions.core.js | 61 -- .../src/cores/player/classes/TrackInstance.js | 127 +++ packages/app/src/cores/player/player.bkp.js | 947 ++++++++++++++++++ packages/app/src/cores/player/player.core.js | 342 ++----- .../app/src/cores/player/processors/node.js | 2 +- packages/app/src/cores/player/services.js | 66 +- packages/app/src/cores/sfx/sfx.core.js | 2 - packages/app/src/cores/style/style.core.jsx | 225 +++-- packages/app/src/hooks/useChat/index.js | 2 +- .../app/src/hooks/useClickNavById/index.js | 39 + .../src/hooks/useHideOnMouseStop/index.jsx | 59 ++ .../{utils => hooks}/useMaxScreen/index.js | 9 +- .../layouts/components/bottomBar/index.jsx | 6 +- .../src/layouts/components/drawer/index.jsx | 2 +- .../src/layouts/components/sidebar/index.jsx | 63 +- .../src/layouts/components/toolsBar/index.jsx | 24 +- .../layouts/components/toolsBar/index.less | 23 +- .../src/layouts/components/topBar/index.jsx | 2 +- .../pages/{marketplace => addons}/index.jsx | 34 +- .../pages/{marketplace => addons}/index.less | 18 +- .../components/BasicInformation/index.jsx | 0 .../music}/components/TracksUploads/index.jsx | 2 +- .../components/TracksUploads/index.less | 0 .../creator => creator/music}/index.jsx | 0 .../creator => creator/music}/index.less | 0 .../lyrics/components/controller/index.jsx | 37 +- .../pages/lyrics/components/text/index.jsx | 30 +- .../pages/lyrics/components/video/index.jsx | 49 +- packages/app/src/pages/lyrics/index.jsx | 32 +- packages/app/src/pages/lyrics/index.less | 12 + .../src/pages/messages/[to_user_id]/index.jsx | 45 +- .../pages/messages/[to_user_id]/index.less | 50 +- packages/app/src/pages/messages/index.jsx | 70 ++ packages/app/src/pages/messages/index.less | 78 ++ .../app/src/pages/music/dashboard/index.jsx | 15 - .../pages/music/dashboard/releases/index.jsx | 206 ---- .../pages/music/dashboard/releases/index.less | 149 --- .../{components => tabs}/explore/index.jsx | 123 +-- .../{components => tabs}/explore/index.less | 53 - .../{components => tabs}/favorites/index.jsx | 0 .../pages/music/{tabs.jsx => tabs/index.jsx} | 6 +- .../{components => tabs}/library/index.jsx | 0 .../{components => tabs}/library/index.less | 0 .../{components => tabs}/spaces/index.jsx | 0 .../{components => tabs}/spaces/index.less | 0 .../components/SettingItemComponent/index.jsx | 24 +- .../SettingItemComponent/index.less | 121 +++ packages/app/src/pages/settings/index.jsx | 8 + packages/app/src/pages/settings/index.less | 104 +- packages/app/src/pages/studio/index.jsx | 55 + packages/app/src/pages/studio/index.less | 78 ++ .../pages/studio/music/[release_id]/index.jsx | 13 + packages/app/src/pages/studio/music/index.jsx | 32 + .../app/src/pages/studio/music/index.less | 25 + packages/app/src/pages/tv/live/[id].jsx | 9 +- .../app/src/settings/accessibility/index.jsx | 40 + packages/app/src/settings/apparence/index.jsx | 328 +----- .../components/backgroundSelector/index.jsx | 2 +- .../components/backgroundTweaker/index.jsx | 313 ++++++ .../components/backgroundTweaker/index.less | 111 ++ .../components/changePassword/index.jsx | 2 +- .../components/themeVariantSelector/index.jsx | 49 + .../themeVariantSelector/index.less | 45 + packages/app/src/settings/general/index.jsx | 51 +- packages/app/src/settings/tap_share/index.jsx | 4 +- .../app/src/utils/checkUserIdIsSelf/index.js | 3 + packages/app/ssl/cert.pem | 25 + packages/app/ssl/chain.pem | 30 + packages/app/ssl/fullchain.pem | 55 + packages/app/ssl/privkey.pem | 6 + packages/app/vite.config.js | 4 + packages/server/classes/Languages/data.json | 611 +++++++++++ packages/server/classes/Languages/index.js | 11 + .../server/db_models/musicLyrics/index.js | 2 +- packages/server/db_models/track/index.js | 21 +- packages/server/gateway/proxy.js | 27 +- packages/server/old_g.js | 467 --------- .../services/chats/routes/chats/my/get.js | 77 ++ .../chats/routes_ws/chat/send/message.js | 12 +- .../chats/routes_ws/chat/state/typing.js | 15 +- .../services/ems/routes/dispatch/post.js | 18 +- .../account_disabled/index.handlebars | 290 ++++++ .../server/services/ems/templates/index.js | 1 + packages/server/services/music/package.json | 1 + .../services/music/routes/music/feed/get.js | 11 +- .../routes/music/lyrics/[track_id]/get.js | 35 +- .../music/routes/music/releases/self/get.js | 30 +- .../users/routes/users/[user_id]/roles/get.js | 12 + .../users/routes/users/self/roles/get.js | 16 + packages/server/ssl/cert.pem | 25 + packages/server/ssl/chain.pem | 30 + packages/server/ssl/fullchain.pem | 55 + packages/server/ssl/privkey.pem | 6 + testExtensions/tidal/package.json | 7 + testExtensions/tidal/src/tidal.extension.js | 9 + 139 files changed, 6491 insertions(+), 2221 deletions(-) create mode 100644 packages/app/config/languages.json rename packages/app/config/translations/{en.json => en_US.json} (100%) rename packages/app/config/translations/{es.json => es_ES.json} (100%) create mode 100644 packages/app/src/components/CoverEditor/index.jsx create mode 100644 packages/app/src/components/CoverEditor/index.less create mode 100644 packages/app/src/components/MusicStudio/LyricsEditor/index.jsx create mode 100644 packages/app/src/components/MusicStudio/LyricsEditor/index.less create mode 100644 packages/app/src/components/MusicStudio/LyricsTextView/index.jsx create mode 100644 packages/app/src/components/MusicStudio/MyReleasesList/index.jsx rename packages/app/src/{pages/music/dashboard => components/MusicStudio/MyReleasesList}/index.less (57%) create mode 100644 packages/app/src/components/MusicStudio/ReleaseEditor/index.jsx create mode 100644 packages/app/src/components/MusicStudio/ReleaseEditor/index.less create mode 100644 packages/app/src/components/MusicStudio/ReleaseEditor/tabs/Advanced/index.jsx create mode 100644 packages/app/src/components/MusicStudio/ReleaseEditor/tabs/BasicInformation/index.jsx create mode 100644 packages/app/src/components/MusicStudio/ReleaseEditor/tabs/Tracks/index.jsx create mode 100644 packages/app/src/components/MusicStudio/ReleaseEditor/tabs/Tracks/index.less create mode 100644 packages/app/src/components/MusicStudio/ReleaseEditor/tabs/index.jsx create mode 100644 packages/app/src/components/MusicStudio/ReleaseItem/index.jsx create mode 100644 packages/app/src/components/MusicStudio/ReleaseItem/index.less create mode 100644 packages/app/src/components/MusicStudio/TrackEditor/index.jsx create mode 100644 packages/app/src/components/MusicStudio/TrackEditor/index.less create mode 100644 packages/app/src/components/MusicStudio/VideoEditor/index.jsx create mode 100644 packages/app/src/components/MusicStudio/VideoEditor/index.less create mode 100644 packages/app/src/components/ReleasesList/index.jsx create mode 100644 packages/app/src/components/ReleasesList/index.less create mode 100644 packages/app/src/components/TimeAgo/index.jsx delete mode 100755 packages/app/src/cores/permissions/permissions.core.js create mode 100644 packages/app/src/cores/player/classes/TrackInstance.js create mode 100755 packages/app/src/cores/player/player.bkp.js create mode 100644 packages/app/src/hooks/useClickNavById/index.js create mode 100644 packages/app/src/hooks/useHideOnMouseStop/index.jsx rename packages/app/src/{utils => hooks}/useMaxScreen/index.js (60%) rename packages/app/src/pages/{marketplace => addons}/index.jsx (68%) rename packages/app/src/pages/{marketplace => addons}/index.less (86%) rename packages/app/src/pages/{music/creator => creator/music}/components/BasicInformation/index.jsx (100%) rename packages/app/src/pages/{music/creator => creator/music}/components/TracksUploads/index.jsx (99%) rename packages/app/src/pages/{music/creator => creator/music}/components/TracksUploads/index.less (100%) rename packages/app/src/pages/{music/creator => creator/music}/index.jsx (100%) rename packages/app/src/pages/{music/creator => creator/music}/index.less (100%) delete mode 100755 packages/app/src/pages/music/dashboard/index.jsx delete mode 100755 packages/app/src/pages/music/dashboard/releases/index.jsx delete mode 100755 packages/app/src/pages/music/dashboard/releases/index.less rename packages/app/src/pages/music/{components => tabs}/explore/index.jsx (66%) rename packages/app/src/pages/music/{components => tabs}/explore/index.less (83%) rename packages/app/src/pages/music/{components => tabs}/favorites/index.jsx (100%) rename packages/app/src/pages/music/{tabs.jsx => tabs/index.jsx} (77%) rename packages/app/src/pages/music/{components => tabs}/library/index.jsx (100%) rename packages/app/src/pages/music/{components => tabs}/library/index.less (100%) rename packages/app/src/pages/music/{components => tabs}/spaces/index.jsx (100%) rename packages/app/src/pages/music/{components => tabs}/spaces/index.less (100%) create mode 100644 packages/app/src/pages/settings/components/SettingItemComponent/index.less create mode 100644 packages/app/src/pages/studio/index.jsx create mode 100644 packages/app/src/pages/studio/index.less create mode 100644 packages/app/src/pages/studio/music/[release_id]/index.jsx create mode 100644 packages/app/src/pages/studio/music/index.jsx create mode 100644 packages/app/src/pages/studio/music/index.less create mode 100644 packages/app/src/settings/accessibility/index.jsx create mode 100644 packages/app/src/settings/components/backgroundTweaker/index.jsx create mode 100644 packages/app/src/settings/components/backgroundTweaker/index.less create mode 100644 packages/app/src/settings/components/themeVariantSelector/index.jsx create mode 100644 packages/app/src/settings/components/themeVariantSelector/index.less create mode 100644 packages/app/src/utils/checkUserIdIsSelf/index.js create mode 100644 packages/app/ssl/cert.pem create mode 100644 packages/app/ssl/chain.pem create mode 100644 packages/app/ssl/fullchain.pem create mode 100644 packages/app/ssl/privkey.pem create mode 100644 packages/server/classes/Languages/data.json create mode 100644 packages/server/classes/Languages/index.js delete mode 100755 packages/server/old_g.js create mode 100644 packages/server/services/chats/routes/chats/my/get.js create mode 100644 packages/server/services/ems/templates/account_disabled/index.handlebars create mode 100644 packages/server/services/users/routes/users/[user_id]/roles/get.js create mode 100644 packages/server/services/users/routes/users/self/roles/get.js create mode 100644 packages/server/ssl/cert.pem create mode 100644 packages/server/ssl/chain.pem create mode 100644 packages/server/ssl/fullchain.pem create mode 100644 packages/server/ssl/privkey.pem create mode 100644 testExtensions/tidal/package.json create mode 100644 testExtensions/tidal/src/tidal.extension.js diff --git a/.vscode/settings.json b/.vscode/settings.json index e7ba0852..99e533d7 100755 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,24 @@ "docify.inlineComments": true, "docify.moreExpressiveComments": true, "docify.sidePanelReviewMode": false, - "docify.programmingLanguage": "javascript" + "docify.programmingLanguage": "javascript", + "workbench.colorCustomizations": { + "activityBar.activeBackground": "#ff9396", + "activityBar.background": "#ff9396", + "activityBar.foreground": "#15202b", + "activityBar.inactiveForeground": "#15202b99", + "activityBarBadge.background": "#048000", + "activityBarBadge.foreground": "#e7e7e7", + "commandCenter.border": "#15202b99", + "sash.hoverBorder": "#ff9396", + "statusBar.background": "#ff6064", + "statusBar.foreground": "#15202b", + "statusBarItem.hoverBackground": "#ff2d32", + "statusBarItem.remoteBackground": "#ff6064", + "statusBarItem.remoteForeground": "#15202b", + "titleBar.activeBackground": "#ff6064", + "titleBar.activeForeground": "#15202b", + "titleBar.inactiveBackground": "#ff606499", + "titleBar.inactiveForeground": "#15202b99" + } } \ No newline at end of file diff --git a/comty.js b/comty.js index 126bad9c..15f89af3 160000 --- a/comty.js +++ b/comty.js @@ -1 +1 @@ -Subproject commit 126bad9c1e21c0c7fcab60a22fc9d70bbbd9a999 +Subproject commit 15f89af37c5bac086e8e1154f5d1b3da8967678b diff --git a/linebridge b/linebridge index 6d553830..d2e6f1bc 160000 --- a/linebridge +++ b/linebridge @@ -1 +1 @@ -Subproject commit 6d553830ab4661ffab952253d77ccb0bfc1363d8 +Subproject commit d2e6f1bc5856e3084d4fd068dec5d67ab2ef9d8d diff --git a/packages/app/config/defaultSettings.json b/packages/app/config/defaultSettings.json index b943653b..e99c7641 100755 --- a/packages/app/config/defaultSettings.json +++ b/packages/app/config/defaultSettings.json @@ -1,5 +1,5 @@ { - "app:language": "en", + "app:language": "en_US", "low_performance_mode": false, "transcode_video_browser": false, "forceMobileMode": false, diff --git a/packages/app/config/index.js b/packages/app/config/index.js index fb02777f..2bdb9cce 100755 --- a/packages/app/config/index.js +++ b/packages/app/config/index.js @@ -56,14 +56,14 @@ export default { i18n: { languages: [ { - locale: "en", + locale: "en_US", name: "English" }, { - locale: "es", + locale: "es_ES", name: "Español" } ], - defaultLocale: "en", + defaultLocale: "en_US", } } \ No newline at end of file diff --git a/packages/app/config/languages.json b/packages/app/config/languages.json new file mode 100644 index 00000000..2074958e --- /dev/null +++ b/packages/app/config/languages.json @@ -0,0 +1,611 @@ +{ + "ab": "Abkhazian", + "ace": "Achinese", + "ach": "Acoli", + "ada": "Adangme", + "ady": "Adyghe", + "aa": "Afar", + "afh": "Afrihili", + "af": "Afrikaans", + "agq": "Aghem", + "ain": "Ainu", + "ak": "Akan", + "akk": "Akkadian", + "bss": "Akoose", + "akz": "Alabama", + "sq": "Albanian", + "ale": "Aleut", + "arq": "Algerian Arabic", + "am": "Amarik", + "en_US": "American English", + "ase": "American Sign Language", + "egy": "Ancient Egyptian", + "grc": "Ancient Greek", + "anp": "Angika", + "njo": "Ao Naga", + "ar": "Arabik", + "an": "Aragonese", + "arc": "Aramaic", + "aro": "Araona", + "arp": "Arapaho", + "arw": "Arawak", + "hy": "Armenian", + "rup": "Aromanian", + "frp": "Arpitan", + "as": "Assamese", + "ast": "Asturian", + "asa": "Asu", + "cch": "Atsam", + "en_AU": "Australian English", + "de_AT": "Austrian German", + "av": "Avaric", + "ae": "Avestan", + "awa": "Awadhi", + "ay": "Aymara", + "az": "Azerbaijani", + "bfq": "Badaga", + "ksf": "Bafia", + "bfd": "Bafut", + "bqi": "Bakhtiari", + "ban": "Balinese", + "bal": "Baluchi", + "bm": "Bambara", + "bax": "Bamun", + "bjn": "Banjar", + "bas": "Basaa", + "ba": "Bashkir", + "eu": "Basque", + "bbc": "Batak Toba", + "bar": "Bavarian", + "bej": "Beja", + "be": "Belarus kasa", + "bem": "Bemba", + "bez": "Bena", + "bn": "Bengali kasa", + "bew": "Betawi", + "my": "B\u025b\u025bmis kasa", + "bho": "Bhojpuri", + "bik": "Bikol", + "bin": "Bini", + "bpy": "Bishnupriya", + "bi": "Bislama", + "byn": "Blin", + "zbl": "Blissymbols", + "brx": "Bodo", + "en": "Bor\u0254fo", + "bs": "Bosnian", + "bg": "B\u0254lgeria kasa", + "brh": "Brahui", + "bra": "Braj", + "pt_BR": "Brazilian Portuguese", + "br": "Breton", + "en_GB": "British English", + "bug": "Buginese", + "bum": "Bulu", + "bua": "Buriat", + "cad": "Caddo", + "frc": "Cajun French", + "en_CA": "Canadian English", + "fr_CA": "Canadian French", + "yue": "Cantonese", + "cps": "Capiznon", + "car": "Carib", + "ca": "Catalan", + "cay": "Cayuga", + "ceb": "Cebuano", + "tzm": "Central Atlas Tamazight", + "dtp": "Central Dusun", + "ckb": "Central Kurdish", + "esu": "Central Yupik", + "shu": "Chadian Arabic", + "chg": "Chagatai", + "ch": "Chamorro", + "ce": "Chechen", + "chr": "Cherokee", + "chy": "Cheyenne", + "chb": "Chibcha", + "cgg": "Chiga", + "qug": "Chimborazo Highland Quichua", + "chn": "Chinook Jargon", + "chp": "Chipewyan", + "cho": "Choctaw", + "cu": "Church Slavic", + "chk": "Chuukese", + "cv": "Chuvash", + "nwc": "Classical Newari", + "syc": "Classical Syriac", + "ksh": "Colognian", + "swb": "Comorian", + "swc": "Congo Swahili", + "cop": "Coptic", + "kw": "Cornish", + "co": "Corsican", + "cr": "Cree", + "mus": "Creek", + "crh": "Crimean Turkish", + "hr": "Croatian", + "dak": "Dakota", + "da": "Danish", + "dar": "Dargwa", + "dzg": "Dazaga", + "del": "Delaware", + "nl": "D\u025b\u025bkye", + "din": "Dinka", + "dv": "Divehi", + "doi": "Dogri", + "dgr": "Dogrib", + "dua": "Duala", + "dyu": "Dyula", + "dz": "Dzongkha", + "frs": "Eastern Frisian", + "efi": "Efik", + "arz": "Egyptian Arabic", + "eka": "Ekajuk", + "elx": "Elamite", + "ebu": "Embu", + "egl": "Emilian", + "myv": "Erzya", + "eo": "Esperanto", + "et": "Estonian", + "pt_PT": "Portuguese", + "es_ES": "Español", + "ee": "Ewe", + "ewo": "Ewondo", + "ext": "Extremaduran", + "fan": "Fang", + "fat": "Fanti", + "fo": "Faroese", + "hif": "Fiji Hindi", + "fj": "Fijian", + "fil": "Filipino", + "fi": "Finnish", + "nl_BE": "Flemish", + "fon": "Fon", + "gur": "Frafra", + "fr": "Fr\u025bnkye", + "fur": "Friulian", + "ff": "Fulah", + "gaa": "Ga", + "gag": "Gagauz", + "gl": "Galician", + "gan": "Gan Chinese", + "lg": "Ganda", + "gay": "Gayo", + "gba": "Gbaya", + "gez": "Geez", + "ka": "Georgian", + "aln": "Gheg Albanian", + "bbj": "Ghomala", + "glk": "Gilaki", + "gil": "Gilbertese", + "gom": "Goan Konkani", + "gon": "Gondi", + "gor": "Gorontalo", + "got": "Gothic", + "grb": "Grebo", + "el": "Greek kasa", + "gn": "Guarani", + "gu": "Gujarati", + "guz": "Gusii", + "gwi": "Gwich\u02bcin", + "de": "Gyaaman", + "jv": "Gyabanis kasa", + "ja": "Gyapan kasa", + "hai": "Haida", + "ht": "Haitian", + "hak": "Hakka Chinese", + "hu": "Hangri kasa", + "ha": "Hausa", + "haw": "Hawaiian", + "he": "Hebrew", + "hz": "Herero", + "hil": "Hiligaynon", + "hi": "Hindi", + "ho": "Hiri Motu", + "hit": "Hittite", + "hmn": "Hmong", + "hup": "Hupa", + "iba": "Iban", + "ibb": "Ibibio", + "is": "Icelandic", + "io": "Ido", + "ig": "Igbo", + "ilo": "Iloko", + "smn": "Inari Sami", + "id": "Indonihyia kasa", + "izh": "Ingrian", + "inh": "Ingush", + "ia": "Interlingua", + "ie": "Interlingue", + "iu": "Inuktitut", + "ik": "Inupiaq", + "ga": "Irish", + "it": "Italy kasa", + "jam": "Jamaican Creole English", + "kaj": "Jju", + "dyo": "Jola-Fonyi", + "jrb": "Judeo-Arabic", + "jpr": "Judeo-Persian", + "jut": "Jutish", + "kbd": "Kabardian", + "kea": "Kabuverdianu", + "kab": "Kabyle", + "kac": "Kachin", + "kgp": "Kaingang", + "kkj": "Kako", + "kl": "Kalaallisut", + "kln": "Kalenjin", + "xal": "Kalmyk", + "kam": "Kamba", + "km": "Kambodia kasa", + "kbl": "Kanembu", + "kn": "Kannada", + "kr": "Kanuri", + "kaa": "Kara-Kalpak", + "krc": "Karachay-Balkar", + "krl": "Karelian", + "ks": "Kashmiri", + "csb": "Kashubian", + "kaw": "Kawi", + "kk": "Kazakh", + "ken": "Kenyang", + "kha": "Khasi", + "kho": "Khotanese", + "khw": "Khowar", + "ki": "Kikuyu", + "kmb": "Kimbundu", + "krj": "Kinaray-a", + "kiu": "Kirmanjki", + "tlh": "Klingon", + "bkm": "Kom", + "kv": "Komi", + "koi": "Komi-Permyak", + "kg": "Kongo", + "kok": "Konkani", + "ko": "Korea kasa", + "kfo": "Koro", + "kos": "Kosraean", + "avk": "Kotava", + "khq": "Koyra Chiini", + "ses": "Koyraboro Senni", + "kpe": "Kpelle", + "kri": "Krio", + "kj": "Kuanyama", + "kum": "Kumyk", + "ku": "Kurdish", + "kru": "Kurukh", + "kut": "Kutenai", + "nmg": "Kwasio", + "zh": "Kyaena kasa", + "cs": "Ky\u025bk kasa", + "ky": "Kyrgyz", + "quc": "K\u02bciche\u02bc", + "lad": "Ladino", + "lah": "Lahnda", + "lkt": "Lakota", + "lam": "Lamba", + "lag": "Langi", + "lo": "Lao", + "ltg": "Latgalian", + "la": "Latin", + "es_419": "Latin American Spanish", + "lv": "Latvian", + "lzz": "Laz", + "lez": "Lezghian", + "lij": "Ligurian", + "li": "Limburgish", + "ln": "Lingala", + "lfn": "Lingua Franca Nova", + "lzh": "Literary Chinese", + "lt": "Lithuanian", + "liv": "Livonian", + "jbo": "Lojban", + "lmo": "Lombard", + "nds": "Low German", + "sli": "Lower Silesian", + "dsb": "Lower Sorbian", + "loz": "Lozi", + "lu": "Luba-Katanga", + "lua": "Luba-Lulua", + "lui": "Luiseno", + "smj": "Lule Sami", + "lun": "Lunda", + "luo": "Luo", + "lb": "Luxembourgish", + "luy": "Luyia", + "mde": "Maba", + "mk": "Macedonian", + "jmc": "Machame", + "mad": "Madurese", + "maf": "Mafa", + "mag": "Magahi", + "vmf": "Main-Franconian", + "mai": "Maithili", + "mak": "Makasar", + "mgh": "Makhuwa-Meetto", + "kde": "Makonde", + "mg": "Malagasy", + "ms": "Malay kasa", + "ml": "Malayalam", + "mt": "Maltese", + "mnc": "Manchu", + "mdr": "Mandar", + "man": "Mandingo", + "mni": "Manipuri", + "gv": "Manx", + "mi": "Maori", + "arn": "Mapuche", + "mr": "Marathi", + "chm": "Mari", + "mh": "Marshallese", + "mwr": "Marwari", + "mas": "Masai", + "mzn": "Mazanderani", + "byv": "Medumba", + "men": "Mende", + "mwv": "Mentawai", + "mer": "Meru", + "mgo": "Meta\u02bc", + "es_MX": "Mexican Spanish", + "mic": "Micmac", + "dum": "Middle Dutch", + "enm": "Middle English", + "frm": "Middle French", + "gmh": "Middle High German", + "mga": "Middle Irish", + "nan": "Min Nan Chinese", + "min": "Minangkabau", + "xmf": "Mingrelian", + "mwl": "Mirandese", + "lus": "Mizo", + "ar_001": "Modern Standard Arabic", + "moh": "Mohawk", + "mdf": "Moksha", + "ro_MD": "Moldavian", + "lol": "Mongo", + "mn": "Mongolian", + "mfe": "Morisyen", + "ary": "Moroccan Arabic", + "mos": "Mossi", + "mul": "Multiple Languages", + "mua": "Mundang", + "ttt": "Muslim Tat", + "mye": "Myene", + "naq": "Nama", + "na": "Nauru", + "nv": "Navajo", + "ng": "Ndonga", + "nap": "Neapolitan", + "new": "Newari", + "ne": "N\u025bpal kasa", + "sba": "Ngambay", + "nnh": "Ngiemboon", + "jgo": "Ngomba", + "yrl": "Nheengatu", + "nia": "Nias", + "niu": "Niuean", + "zxx": "No linguistic content", + "nog": "Nogai", + "nd": "North Ndebele", + "frr": "Northern Frisian", + "se": "Northern Sami", + "nso": "Northern Sotho", + "no": "Norwegian", + "nb": "Norwegian Bokm\u00e5l", + "nn": "Norwegian Nynorsk", + "nov": "Novial", + "nus": "Nuer", + "nym": "Nyamwezi", + "ny": "Nyanja", + "nyn": "Nyankole", + "tog": "Nyasa Tonga", + "nyo": "Nyoro", + "nzi": "Nzima", + "nqo": "N\u02bcKo", + "oc": "Occitan", + "oj": "Ojibwa", + "ang": "Old English", + "fro": "Old French", + "goh": "Old High German", + "sga": "Old Irish", + "non": "Old Norse", + "peo": "Old Persian", + "pro": "Old Proven\u00e7al", + "or": "Oriya", + "om": "Oromo", + "osa": "Osage", + "os": "Ossetic", + "ota": "Ottoman Turkish", + "pal": "Pahlavi", + "pfl": "Palatine German", + "pau": "Palauan", + "pi": "Pali", + "pam": "Pampanga", + "pag": "Pangasinan", + "pap": "Papiamento", + "ps": "Pashto", + "pdc": "Pennsylvania German", + "fa": "P\u025b\u025bhyia kasa", + "phn": "Phoenician", + "pcd": "Picard", + "pms": "Piedmontese", + "pdt": "Plautdietsch", + "pon": "Pohnpeian", + "pnt": "Pontic", + "pl": "P\u0254land kasa", + "pt": "P\u0254\u0254tugal kasa", + "prg": "Prussian", + "pa": "Pungyabi kasa", + "qu": "Quechua", + "ru": "Rahyia kasa", + "raj": "Rajasthani", + "rap": "Rapanui", + "rar": "Rarotongan", + "rw": "Rewanda kasa", + "rif": "Riffian", + "rgn": "Romagnol", + "rm": "Romansh", + "rom": "Romany", + "rof": "Rombo", + "ro": "Romenia kasa", + "root": "Root", + "rtm": "Rotuman", + "rug": "Roviana", + "rn": "Rundi", + "rue": "Rusyn", + "rwk": "Rwa", + "ssy": "Saho", + "sah": "Sakha", + "sam": "Samaritan Aramaic", + "saq": "Samburu", + "sm": "Samoan", + "sgs": "Samogitian", + "sad": "Sandawe", + "sg": "Sango", + "sbp": "Sangu", + "sa": "Sanskrit", + "sat": "Santali", + "sc": "Sardinian", + "sas": "Sasak", + "sdc": "Sassarese Sardinian", + "stq": "Saterland Frisian", + "saz": "Saurashtra", + "sco": "Scots", + "gd": "Scottish Gaelic", + "sly": "Selayar", + "sel": "Selkup", + "seh": "Sena", + "see": "Seneca", + "sr": "Serbian", + "sh": "Serbo-Croatian", + "srr": "Serer", + "sei": "Seri", + "ksb": "Shambala", + "shn": "Shan", + "sn": "Shona", + "ii": "Sichuan Yi", + "scn": "Sicilian", + "sid": "Sidamo", + "bla": "Siksika", + "szl": "Silesian", + "zh_Hans": "Simplified Chinese", + "sd": "Sindhi", + "si": "Sinhala", + "sms": "Skolt Sami", + "den": "Slave", + "sk": "Slovak", + "sl": "Slovenian", + "xog": "Soga", + "sog": "Sogdien", + "so": "Somalia kasa", + "snk": "Soninke", + "azb": "South Azerbaijani", + "nr": "South Ndebele", + "alt": "Southern Altai", + "sma": "Southern Sami", + "st": "Southern Sotho", + "es": "Spain kasa", + "srn": "Sranan Tongo", + "zgh": "Standard Moroccan Tamazight", + "suk": "Sukuma", + "sux": "Sumerian", + "su": "Sundanese", + "sus": "Susu", + "sw": "Swahili", + "ss": "Swati", + "sv": "Sweden kasa", + "fr_CH": "Swiss French", + "gsw": "Swiss German", + "de_CH": "Swiss High German", + "syr": "Syriac", + "shi": "Tachelhit", + "th": "Taeland kasa", + "tl": "Tagalog", + "ty": "Tahitian", + "dav": "Taita", + "tg": "Tajik", + "tly": "Talysh", + "tmh": "Tamashek", + "ta": "Tamil kasa", + "trv": "Taroko", + "twq": "Tasawaq", + "tt": "Tatar", + "te": "Telugu", + "ter": "Tereno", + "teo": "Teso", + "tet": "Tetum", + "tr": "T\u025b\u025bki kasa", + "bo": "Tibetan", + "tig": "Tigre", + "ti": "Tigrinya", + "tem": "Timne", + "tiv": "Tiv", + "tli": "Tlingit", + "tpi": "Tok Pisin", + "tkl": "Tokelau", + "to": "Tongan", + "fit": "Tornedalen Finnish", + "zh_Hant": "Traditional Chinese", + "tkr": "Tsakhur", + "tsd": "Tsakonian", + "tsi": "Tsimshian", + "ts": "Tsonga", + "tn": "Tswana", + "tcy": "Tulu", + "tum": "Tumbuka", + "aeb": "Tunisian Arabic", + "tk": "Turkmen", + "tru": "Turoyo", + "tvl": "Tuvalu", + "tyv": "Tuvinian", + "tw": "Twi", + "kcg": "Tyap", + "udm": "Udmurt", + "uga": "Ugaritic", + "uk": "Ukren kasa", + "umb": "Umbundu", + "und": "Unknown Language", + "hsb": "Upper Sorbian", + "ur": "Urdu kasa", + "ug": "Uyghur", + "uz": "Uzbek", + "vai": "Vai", + "ve": "Venda", + "vec": "Venetian", + "vep": "Veps", + "vi": "Vi\u025btnam kasa", + "vo": "Volap\u00fck", + "vro": "V\u00f5ro", + "vot": "Votic", + "vun": "Vunjo", + "wa": "Walloon", + "wae": "Walser", + "war": "Waray", + "wbp": "Warlpiri", + "was": "Washo", + "guc": "Wayuu", + "cy": "Welsh", + "vls": "West Flemish", + "fy": "Western Frisian", + "mrj": "Western Mari", + "wal": "Wolaytta", + "wo": "Wolof", + "wuu": "Wu Chinese", + "xh": "Xhosa", + "hsn": "Xiang Chinese", + "yav": "Yangben", + "yao": "Yao", + "yap": "Yapese", + "ybb": "Yemba", + "yi": "Yiddish", + "yo": "Yoruba", + "zap": "Zapotec", + "dje": "Zarma", + "zza": "Zaza", + "zea": "Zeelandic", + "zen": "Zenaga", + "za": "Zhuang", + "gbz": "Zoroastrian Dari", + "zu": "Zulu", + "zun": "Zuni" +} \ No newline at end of file diff --git a/packages/app/config/sidebar.json b/packages/app/config/sidebar.json index 1767a04a..435ef2b6 100755 --- a/packages/app/config/sidebar.json +++ b/packages/app/config/sidebar.json @@ -1,9 +1,15 @@ [ + { + "id": "home", + "path": "/", + "title": "Home", + "icon": "Home" + }, { "id": "timeline", "path": "/", "title": "Timeline", - "icon": "Home" + "icon": "MdTag" }, { "id": "tv", @@ -16,18 +22,5 @@ "path": "/music", "title": "Music", "icon": "MdMusicNote" - }, - { - "id": "groups", - "path": "/groups", - "title": "Groups", - "icon": "MdGroups", - "disabled": true - }, - { - "id": "Marketplace", - "path": "/marketplace", - "title": "Marketplace", - "icon": "Box" } ] \ No newline at end of file diff --git a/packages/app/config/translations/en.json b/packages/app/config/translations/en_US.json similarity index 100% rename from packages/app/config/translations/en.json rename to packages/app/config/translations/en_US.json diff --git a/packages/app/config/translations/es.json b/packages/app/config/translations/es_ES.json similarity index 100% rename from packages/app/config/translations/es.json rename to packages/app/config/translations/es_ES.json diff --git a/packages/app/package.json b/packages/app/package.json index b904d703..85255a9d 100755 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -6,13 +6,9 @@ "author": "RageStudio", "description": "A prototype of a social network.", "scripts": { - "start": "electron-forge start", "build": "vite build", "dev": "vite", - "dev:electron": "concurrently -k \"yarn dev\" \"yarn electron:dev\"", - "electron:dev": "cross-env IS_DEV=true electron-forge start", "docker-compose:update_run": "docker-compose down && git pull && yarn build && docker-compose up -d --build", - "electron:build": "cross-env IS_DEV=false electron-builder -mwl --dir", "preview": "vite preview" }, "peerDependencies": { diff --git a/packages/app/src-tauri/tauri.conf.json b/packages/app/src-tauri/tauri.conf.json index 031150e6..8da62c65 100644 --- a/packages/app/src-tauri/tauri.conf.json +++ b/packages/app/src-tauri/tauri.conf.json @@ -1,6 +1,6 @@ { "build": { - "devPath": "http://fr01.ragestudio.net:8000", + "devPath": "https://fr01.ragestudio.net:8000", "distDir": "../dist" }, "package": { diff --git a/packages/app/src/App.jsx b/packages/app/src/App.jsx index e8bca0a1..214683da 100755 --- a/packages/app/src/App.jsx +++ b/packages/app/src/App.jsx @@ -106,7 +106,7 @@ class ComtyApp extends React.Component { } }, openLoginForm: async (options = {}) => { - app.DrawerController.open("login", Login, { + app.layout.drawer.open("login", Login, { defaultLocked: options.defaultLocked ?? false, componentProps: { sessionController: this.sessionController, @@ -120,7 +120,7 @@ class ComtyApp extends React.Component { }) }, openRegisterForm: async (options = {}) => { - app.DrawerController.open("Register", UserRegister, { + app.layout.drawer.open("Register", UserRegister, { defaultLocked: options.defaultLocked ?? false, componentProps: { sessionController: this.sessionController, @@ -144,7 +144,7 @@ class ComtyApp extends React.Component { }, openSearcher: (options) => { if (app.isMobile) { - return app.DrawerController.open("searcher", Searcher, { + return app.layout.drawer.open("searcher", Searcher, { ...options, componentProps: { renderResults: true, diff --git a/packages/app/src/components/BackgroundDecorator/index.jsx b/packages/app/src/components/BackgroundDecorator/index.jsx index e126654b..4d568013 100755 --- a/packages/app/src/components/BackgroundDecorator/index.jsx +++ b/packages/app/src/components/BackgroundDecorator/index.jsx @@ -23,7 +23,7 @@ export default () => { React.useEffect(() => { app.eventBus.on("style.update", handleStyleUpdate) - const activeSVG = app.cores.style.getValue("backgroundSVG") + const activeSVG = app.cores.style.getVar("backgroundSVG") if (hasBackgroundSVG(activeSVG)) { setActiveColor(true) diff --git a/packages/app/src/components/CoverEditor/index.jsx b/packages/app/src/components/CoverEditor/index.jsx new file mode 100644 index 00000000..eef48241 --- /dev/null +++ b/packages/app/src/components/CoverEditor/index.jsx @@ -0,0 +1,58 @@ +import React from "react" +import * as antd from "antd" + +import Image from "@components/Image" +import UploadButton from "@components/UploadButton" + +import "./index.less" + +const CoverEditor = (props) => { + const { value, onChange, defaultUrl } = props + + const [url, setUrl] = React.useState(value) + + React.useEffect(() => { + setUrl(value) + }, [value]) + + React.useEffect(() => { + onChange(url) + }, [url]) + + React.useEffect(() => { + if (!url) { + setUrl(defaultUrl) + } + }, []) + + return
+
+ +
+ +
+ { + setUrl(response.url) + }} + /> + + { + setUrl(defaultUrl) + }} + > + Reset + + + { + props.extraActions + } +
+
+} + +export default CoverEditor diff --git a/packages/app/src/components/CoverEditor/index.less b/packages/app/src/components/CoverEditor/index.less new file mode 100644 index 00000000..6d4dd835 --- /dev/null +++ b/packages/app/src/components/CoverEditor/index.less @@ -0,0 +1,45 @@ +.cover-editor { + display: flex; + + flex-direction: column; + width: 100%; + + gap: 10px; + + .cover-editor-preview { + display: flex; + flex-direction: row; + + align-items: center; + justify-content: center; + + width: 100%; + + padding: 7px; + + background-color: var(--background-color-accent); + + border-radius: 12px; + + .lazy-load-image-background { + max-width: 200px; + max-height: 200px; + + img { + height: 100%; + width: 100%; + + border-radius: 12px; + + object-fit: contain; + } + } + } + + .cover-editor-actions { + display: flex; + flex-direction: row; + + gap: 20px; + } +} \ No newline at end of file diff --git a/packages/app/src/components/Login/index.jsx b/packages/app/src/components/Login/index.jsx index 2e992718..a46436d8 100755 --- a/packages/app/src/components/Login/index.jsx +++ b/packages/app/src/components/Login/index.jsx @@ -113,20 +113,6 @@ class Login extends React.Component { app.location.push("/apr") } - onClickRegister = () => { - if (this.props.locked) { - this.props.unlock() - } - - if (typeof this.props.close === "function") { - this.props.close() - } - - app.controls.openRegisterForm({ - defaultLocked: this.props.locked - }) - } - toggleLoading = (to) => { if (typeof to === "undefined") { to = !this.state.loading @@ -351,10 +337,6 @@ class Login extends React.Component {
Forgot your password?
- -
- You need a account? -
} diff --git a/packages/app/src/components/Music/PlaylistView/index.jsx b/packages/app/src/components/Music/PlaylistView/index.jsx index 4ef1530b..7712a136 100755 --- a/packages/app/src/components/Music/PlaylistView/index.jsx +++ b/packages/app/src/components/Music/PlaylistView/index.jsx @@ -5,11 +5,12 @@ import ReactMarkdown from "react-markdown" import remarkGfm from "remark-gfm" import fuse from "fuse.js" -import useWsEvents from "@hooks/useWsEvents" - import { WithPlayerContext } from "@contexts/WithPlayerContext" import { Context as PlaylistContext } from "@contexts/WithPlaylistContext" +import useWsEvents from "@hooks/useWsEvents" +import checkUserIdIsSelf from "@utils/checkUserIdIsSelf" + import LoadMore from "@components/LoadMore" import { Icons } from "@components/Icons" import MusicTrack from "@components/Music/Track" @@ -75,7 +76,7 @@ const MoreMenuHandlers = { export default (props) => { const [playlist, setPlaylist] = React.useState(props.playlist) const [searchResults, setSearchResults] = React.useState(null) - const [owningPlaylist, setOwningPlaylist] = React.useState(app.cores.permissions.checkUserIdIsSelf(props.playlist?.user_id)) + const [owningPlaylist, setOwningPlaylist] = React.useState(checkUserIdIsSelf(props.playlist?.user_id)) const moreMenuItems = React.useMemo(() => { const items = [{ @@ -84,7 +85,7 @@ export default (props) => { }] if (!playlist.type || playlist.type === "playlist") { - if (app.cores.permissions.checkUserIdIsSelf(playlist.user_id)) { + if (checkUserIdIsSelf(playlist.user_id)) { items.push({ key: "delete", label: "Delete", @@ -221,7 +222,7 @@ export default (props) => { React.useEffect(() => { setPlaylist(props.playlist) - setOwningPlaylist(app.cores.permissions.checkUserIdIsSelf(props.playlist?.user_id)) + setOwningPlaylist(checkUserIdIsSelf(props.playlist?.user_id)) }, [props.playlist]) if (!playlist) { diff --git a/packages/app/src/components/MusicStudio/LyricsEditor/index.jsx b/packages/app/src/components/MusicStudio/LyricsEditor/index.jsx new file mode 100644 index 00000000..e46bbd81 --- /dev/null +++ b/packages/app/src/components/MusicStudio/LyricsEditor/index.jsx @@ -0,0 +1,116 @@ +import React from "react" +import * as antd from "antd" + +import LyricsTextView from "../LyricsTextView" +import UploadButton from "@components/UploadButton" +import { Icons } from "@components/Icons" + +import MusicService from "@models/music" + +import Languages from "@config/languages" + +const LanguagesMap = Object.entries(Languages).map(([key, value]) => { + return { + label: value, + value: key, + } +}) + +import "./index.less" + +const LyricsEditor = (props) => { + const [L_TrackLyrics, R_TrackLyrics, E_TrackLyrics, F_TrackLyrics] = app.cores.api.useRequest(MusicService.getTrackLyrics, props.track._id) + + const [langs, setLangs] = React.useState([]) + const [selectedLang, setSelectedLang] = React.useState("original") + + async function onUploadLRC(uid, data) { + const { url } = data + + setLangs((prev) => { + const index = prev.findIndex((lang) => { + return lang.id === selectedLang + }) + + console.log(`Replacing value for id [${selectedLang}] at index [${index}]`) + + if (index !== -1) { + prev[index].value = url + } else { + const lang = LanguagesMap.find((lang) => { + return lang.value === selectedLang + }) + + prev.push({ + id: lang.value, + name: lang.label, + value: url + }) + } + + console.log(`new value =>`, prev) + + return prev + }) + } + + React.useEffect(() => { + if (R_TrackLyrics) { + if (R_TrackLyrics.available_langs) { + setLangs(R_TrackLyrics.available_langs) + } + } + console.log(R_TrackLyrics) + }, [R_TrackLyrics]) + + const currentLangData = selectedLang && langs.find((lang) => { + return lang.id === selectedLang + }) + + console.log(langs, currentLangData) + + return
+

Lyrics

+ + (option?.label.toLowerCase() ?? '').includes(input.toLowerCase())} + filterSort={(optionA, optionB) => + (optionA?.label.toLowerCase() ?? '').toLowerCase().localeCompare((optionB?.label.toLowerCase() ?? '').toLowerCase()) + } + onChange={setSelectedLang} + /> + + + {selectedLang} + + + { + selectedLang && + } + + { + currentLangData && currentLangData?.value && + } + { + !currentLangData || !currentLangData?.value && + } +
+} + +export default LyricsEditor \ No newline at end of file diff --git a/packages/app/src/components/MusicStudio/LyricsEditor/index.less b/packages/app/src/components/MusicStudio/LyricsEditor/index.less new file mode 100644 index 00000000..e69de29b diff --git a/packages/app/src/components/MusicStudio/LyricsTextView/index.jsx b/packages/app/src/components/MusicStudio/LyricsTextView/index.jsx new file mode 100644 index 00000000..05f5f728 --- /dev/null +++ b/packages/app/src/components/MusicStudio/LyricsTextView/index.jsx @@ -0,0 +1,60 @@ +import React from "react" +import * as antd from "antd" +import axios from "axios" + +const LyricsTextView = (props) => { + const { lang, track } = props + + const [loading, setLoading] = React.useState(false) + const [error, setError] = React.useState(null) + const [lyrics, setLyrics] = React.useState(null) + + async function getLyrics(resource_url) { + setError(null) + setLoading(true) + setLyrics(null) + + const data = await axios({ + method: "get", + url: resource_url, + responseType: "text" + }).catch((err) => { + console.error(err) + setError(err) + + return null + }) + + if (data) { + setLyrics(data.data) + } + + setLoading(false) + } + + React.useEffect(() => { + getLyrics(lang.value) + }, [lang]) + + if (!lang) { + return null + } + + if (error) { + return + } + + if (loading) { + return + } + + return
+

{lyrics}

+
+} + +export default LyricsTextView \ No newline at end of file diff --git a/packages/app/src/components/MusicStudio/MyReleasesList/index.jsx b/packages/app/src/components/MusicStudio/MyReleasesList/index.jsx new file mode 100644 index 00000000..973a515b --- /dev/null +++ b/packages/app/src/components/MusicStudio/MyReleasesList/index.jsx @@ -0,0 +1,55 @@ +import React from "react" +import * as antd from "antd" + +import ReleaseItem from "@components/MusicStudio/ReleaseItem" + +import MusicModel from "@models/music" + +import "./index.less" + +const MyReleasesList = () => { + const [L_MyReleases, R_MyReleases, E_MyReleases, M_MyReleases] = app.cores.api.useRequest(MusicModel.getMyReleases, { + offset: 0, + limit: 100, + }) + + async function onClickReleaseItem(release) { + app.location.push(`/studio/music/${release._id}`) + } + + return
+
+

Your Releases

+
+ + { + L_MyReleases && !E_MyReleases && + } + { + E_MyReleases && + } + { + !L_MyReleases && !E_MyReleases && R_MyReleases && R_MyReleases.items.length === 0 && + } + + { + !L_MyReleases && !E_MyReleases && R_MyReleases && R_MyReleases.items.length > 0 &&
+ { + R_MyReleases.items.map((item) => { + return + }) + } +
+ } +
+} + +export default MyReleasesList \ No newline at end of file diff --git a/packages/app/src/pages/music/dashboard/index.less b/packages/app/src/components/MusicStudio/MyReleasesList/index.less similarity index 57% rename from packages/app/src/pages/music/dashboard/index.less rename to packages/app/src/components/MusicStudio/MyReleasesList/index.less index 04a7f541..e6bf1d52 100644 --- a/packages/app/src/pages/music/dashboard/index.less +++ b/packages/app/src/components/MusicStudio/MyReleasesList/index.less @@ -1,8 +1,8 @@ -.music-dashboard { +.music-studio-page-releases-list { display: flex; flex-direction: column; width: 100%; - .music-dashboard_header {} + gap: 10px; } \ No newline at end of file diff --git a/packages/app/src/components/MusicStudio/ReleaseEditor/index.jsx b/packages/app/src/components/MusicStudio/ReleaseEditor/index.jsx new file mode 100644 index 00000000..ea5b1628 --- /dev/null +++ b/packages/app/src/components/MusicStudio/ReleaseEditor/index.jsx @@ -0,0 +1,107 @@ +import React from "react" +import * as antd from "antd" + +import { Icons } from "@components/Icons" + +import MusicModel from "@models/music" + +import Tabs from "./tabs" + +import "./index.less" + +const ReleaseEditor = (props) => { + const { release_id } = props + + const basicInfoRef = React.useRef() + + const [selectedTab, setSelectedTab] = React.useState("info") + const [L_Release, R_Release, E_Release, F_Release] = release_id !== "new" ? app.cores.api.useRequest(MusicModel.getReleaseData, release_id) : [false, false, false, false] + + async function handleSubmit() { + basicInfoRef.current.submit() + } + + async function onFinish(values) { + console.log(values) + } + + async function canFinish() { + return true + } + + if (E_Release) { + return + } + + if (L_Release) { + return + } + + const Tab = Tabs.find(({ key }) => key === selectedTab) + + return
+
+ setSelectedTab(e.key)} + selectedKeys={[selectedTab]} + items={Tabs} + mode="vertical" + /> + +
+ } + disabled={L_Release || !canFinish()} + > + Save + + + { + release_id !== "new" ? } + disabled={L_Release} + > + Delete + : null + } + + { + release_id !== "new" ? } + onClick={() => app.location.push(`/music/release/${R_Release._id}`)} + > + Go to release + : null + } +
+
+ +
+ { + !Tab && + } + { + Tab && React.createElement(Tab.render, { + release: R_Release, + onFinish: onFinish, + + references: { + basic: basicInfoRef + } + }) + } +
+
+} + +export default ReleaseEditor \ No newline at end of file diff --git a/packages/app/src/components/MusicStudio/ReleaseEditor/index.less b/packages/app/src/components/MusicStudio/ReleaseEditor/index.less new file mode 100644 index 00000000..671d6882 --- /dev/null +++ b/packages/app/src/components/MusicStudio/ReleaseEditor/index.less @@ -0,0 +1,93 @@ +.music-studio-release-editor { + display: flex; + flex-direction: row; + + width: 100%; + + padding: 20px; + + gap: 20px; + + .music-studio-release-editor-header { + display: flex; + flex-direction: row; + + align-items: center; + + gap: 20px; + + .title { + font-size: 1.7rem; + font-family: "Space Grotesk", sans-serif; + } + } + + .music-studio-release-editor-menu { + display: flex; + flex-direction: column; + + gap: 20px; + + align-items: center; + + .ant-btn { + width: 100%; + } + + .ant-menu { + background-color: var(--background-color-accent) !important; + border-radius: 12px; + + padding: 8px; + + gap: 5px; + + .ant-menu-item { + padding: 5px 10px !important; + } + + .ant-menu-item-selected { + background-color: var(--background-color-primary-2) !important; + } + } + + .music-studio-release-editor-menu-actions { + display: flex; + flex-direction: column; + + gap: 10px; + + width: 100%; + } + } + + .music-studio-release-editor-content { + display: flex; + flex-direction: column; + + width: 100%; + + .music-studio-release-editor-tab { + display: flex; + flex-direction: column; + + gap: 10px; + + h1 { + margin: 0; + } + + .ant-form-item { + margin-bottom: 10px; + } + + label { + height: fit-content; + + span { + font-weight: 600; + } + } + } + } +} \ No newline at end of file diff --git a/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/Advanced/index.jsx b/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/Advanced/index.jsx new file mode 100644 index 00000000..23d67faa --- /dev/null +++ b/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/Advanced/index.jsx @@ -0,0 +1,9 @@ +import React from "react" + +const ReleaseAdvanced = (props) => { + return
+

Advanced

+
+} + +export default ReleaseAdvanced \ No newline at end of file diff --git a/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/BasicInformation/index.jsx b/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/BasicInformation/index.jsx new file mode 100644 index 00000000..af4205dd --- /dev/null +++ b/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/BasicInformation/index.jsx @@ -0,0 +1,105 @@ +import React from "react" +import * as antd from "antd" + +import { Icons } from "@components/Icons" + +import CoverEditor from "@components/CoverEditor" + +const ReleasesTypes = [ + { + value: "single", + label: "Single", + icon: , + }, + { + value: "ep", + label: "Episode", + icon: , + }, + { + value: "album", + label: "Album", + icon: , + }, + { + value: "compilation", + label: "Compilation", + icon: , + } +] + +const BasicInformation = (props) => { + const { release, onFinish } = props + + return
+

Release Information

+ + + + + + + { + release._id && ID} + name="_id" + initialValue={release._id} + disabled + > + + + } + + Title} + name="title" + rules={[{ required: true, message: "Input a title for the release" }]} + initialValue={release?.title} + > + + + + Type} + name="type" + rules={[{ required: true, message: "Select a type for the release" }]} + initialValue={release?.type} + > + + + + Public} + name="public" + initialValue={release?.public} + > + + + +
+} + +export default BasicInformation \ No newline at end of file diff --git a/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/Tracks/index.jsx b/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/Tracks/index.jsx new file mode 100644 index 00000000..2ccebcf0 --- /dev/null +++ b/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/Tracks/index.jsx @@ -0,0 +1,249 @@ +import React from "react" +import * as antd from "antd" +import classnames from "classnames" +import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd" + +import { Icons } from "@components/Icons" +import TrackEditor from "@components/MusicStudio/TrackEditor" + +import "./index.less" + +const UploadHint = (props) => { + return
+ +

Upload your tracks

+

Drag and drop your tracks here or click this box to start uploading files.

+
+} + +const TrackListItem = (props) => { + const [loading, setLoading] = React.useState(false) + const [error, setError] = React.useState(null) + + const { track } = props + + async function onClickEditTrack() { + app.layout.drawer.open("track_editor", TrackEditor, { + type: "drawer", + props: { + width: "600px", + headerStyle: { + display: "none", + } + }, + componentProps: { + track, + onSave: (newTrackData) => { + console.log("Saving track", newTrackData) + }, + }, + }) + } + + return + { + (provided, snapshot) => { + return
+
+ {props.index + 1} +
+ + {track.title} + +
+ } + onClick={onClickEditTrack} + /> + +
+ +
+
+
+ } + } +
+} + +const ReleaseTracks = (props) => { + const { release } = props + + const [list, setList] = React.useState(release.list ?? []) + const [pendingTracksUpload, setPendingTracksUpload] = React.useState([]) + + async function onTrackUploaderChange (change) { + switch (change.file.status) { + case "uploading": { + if (!pendingTracksUpload.includes(change.file.uid)) { + pendingTracksUpload.push(change.file.uid) + } + + setList((prev) => { + return [ + ...prev, + + ] + }) + + break + } + case "done": { + // remove pending file + this.setState({ + pendingTracksUpload: this.state.pendingTracksUpload.filter((uid) => uid !== change.file.uid) + }) + + // update file url in the track info + const track = this.state.trackList.find((file) => file.uid === change.file.uid) + + if (track) { + track.source = change.file.response.url + track.status = "done" + } + + this.setState({ + trackList: this.state.trackList + }) + + break + } + case "error": { + // remove pending file + this.handleTrackRemove(change.file.uid) + + // open a dialog to show the error and ask user to retry + antd.Modal.error({ + title: "Upload failed", + content: "An error occurred while uploading the file. You want to retry?", + cancelText: "No", + okText: "Retry", + onOk: () => { + this.handleUploadTrack(change) + }, + onCancel: () => { + this.handleTrackRemove(change.file.uid) + } + }) + } + case "removed": { + this.handleTrackRemove(change.file.uid) + } + + default: { + break + } + } + } + + async function handleUploadTrack (req) { + const response = await app.cores.remoteStorage.uploadFile(req.file, { + onProgress: this.handleFileProgress, + service: "premium-cdn" + }).catch((error) => { + console.error(error) + antd.message.error(error) + + req.onError(error) + + return false + }) + + if (response) { + req.onSuccess(response) + } + } + + async function onTrackDragEnd(result) { + console.log(result) + + if (!result.destination) { + return + } + + setList((prev) => { + const trackList = [...prev] + + const [removed] = trackList.splice(result.source.index, 1) + + trackList.splice(result.destination.index, 0, removed) + + return trackList + }) + } + + return
+

Tracks

+ +
+ + { + list.length === 0 ? + : } + /> + } + + + + + {(provided, snapshot) => ( +
+ { + list.length === 0 && + } + { + list.map((track, index) => { + return + }) + } + {provided.placeholder} +
+ )} +
+
+
+
+} + +export default ReleaseTracks \ No newline at end of file diff --git a/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/Tracks/index.less b/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/Tracks/index.less new file mode 100644 index 00000000..08b787f4 --- /dev/null +++ b/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/Tracks/index.less @@ -0,0 +1,52 @@ +.music-studio-release-editor-tracks-list { + display: flex; + flex-direction: column; + + gap: 10px; +} + +.music-studio-release-editor-tracks-list-item { + position: relative; + + display: flex; + flex-direction: row; + + padding: 10px; + + gap: 10px; + + border-radius: 12px; + + background-color: var(--background-color-accent); + + .music-studio-release-editor-tracks-list-item-actions { + position: absolute; + + top: 0; + right: 0; + + display: flex; + + align-items: center; + justify-content: center; + + height: 100%; + + padding: 0 5px; + + svg { + margin: 0; + } + + .music-studio-release-editor-tracks-list-item-dragger { + display: flex; + + align-items: center; + justify-content: center; + + svg { + font-size: 1rem; + } + } + } +} \ No newline at end of file diff --git a/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/index.jsx b/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/index.jsx new file mode 100644 index 00000000..666d1599 --- /dev/null +++ b/packages/app/src/components/MusicStudio/ReleaseEditor/tabs/index.jsx @@ -0,0 +1,26 @@ +import { Icons, createIconRender } from "@components/Icons" + +import BasicInformation from "./BasicInformation" +import Tracks from "./Tracks" +import Advanced from "./Advanced" + +export default [ + { + key: "info", + label: "Info", + icon: , + render: BasicInformation, + }, + { + key: "tracks", + label: "Tracks", + icon: , + render: Tracks, + }, + { + key: "advanced", + label: "Advanced", + icon: , + render: Advanced, + } +] \ No newline at end of file diff --git a/packages/app/src/components/MusicStudio/ReleaseItem/index.jsx b/packages/app/src/components/MusicStudio/ReleaseItem/index.jsx new file mode 100644 index 00000000..431f6a15 --- /dev/null +++ b/packages/app/src/components/MusicStudio/ReleaseItem/index.jsx @@ -0,0 +1,51 @@ +import React from "react" + +import { Icons } from "@components/Icons" +import Image from "@components/Image" + +import "./index.less" + +const ReleaseItem = (props) => { + const { release, onClick } = props + + async function handleOnClick() { + if (typeof onClick === "function") { + return onClick(release) + } + } + + return
+
+ + + {release.title} +
+ +
+
+ + {release.type} +
+ +
+ + {release._id} +
+ + {/*
+ + {release.analytics?.listen_count ?? 0} +
*/} +
+
+} + +export default ReleaseItem \ No newline at end of file diff --git a/packages/app/src/components/MusicStudio/ReleaseItem/index.less b/packages/app/src/components/MusicStudio/ReleaseItem/index.less new file mode 100644 index 00000000..115c74d7 --- /dev/null +++ b/packages/app/src/components/MusicStudio/ReleaseItem/index.less @@ -0,0 +1,71 @@ +.music-studio-page-release { + display: flex; + flex-direction: column; + + justify-content: center; + + width: 100%; + + padding: 7px; + + gap: 7px; + + background-color: var(--background-color-accent); + + border-radius: 12px; + + transition: all 150ms ease-in-out; + + &:hover { + cursor: pointer; + background-color: var(--background-color-accent-hover); + outline: 2px solid var(--border-color); + } + + .music-studio-page-release-title { + display: flex; + flex-direction: row; + + align-items: center; + + gap: 10px; + + .lazy-load-image-background { + width: 40px; + height: 40px; + + border-radius: 8px; + + overflow: hidden; + + img { + width: 100%; + height: 100%; + } + } + } + + .music-studio-page-release-info { + display: flex; + flex-direction: row; + + gap: 10px; + + text-transform: uppercase; + + font-size: 12px; + + .music-studio-page-release-info-field { + display: flex; + flex-direction: row; + + align-items: center; + + gap: 5px; + + svg { + margin: 0; + } + } + } +} \ No newline at end of file diff --git a/packages/app/src/components/MusicStudio/TrackEditor/index.jsx b/packages/app/src/components/MusicStudio/TrackEditor/index.jsx new file mode 100644 index 00000000..ce290e28 --- /dev/null +++ b/packages/app/src/components/MusicStudio/TrackEditor/index.jsx @@ -0,0 +1,208 @@ +import React from "react" +import * as antd from "antd" + +import CoverEditor from "@components/CoverEditor" +import { Icons } from "@components/Icons" + +import LyricsEditor from "@components/MusicStudio/LyricsEditor" +import VideoEditor from "@components/MusicStudio/VideoEditor" + +import "./index.less" + +const TrackEditor = (props) => { + const [track, setTrack] = React.useState(props.track ?? {}) + + async function handleChange(key, value) { + setTrack((prev) => { + return { + ...prev, + [key]: value + } + }) + } + + async function openLyricsEditor() { + app.layout.drawer.open("lyrics_editor", LyricsEditor, { + type: "drawer", + props: { + width: "600px", + headerStyle: { + display: "none", + } + }, + componentProps: { + track, + onSave: (lyrics) => { + console.log("Saving lyrics for track >", lyrics) + }, + }, + }) + } + + async function openVideoEditor() { + app.layout.drawer.open("video_editor", VideoEditor, { + type: "drawer", + props: { + width: "600px", + headerStyle: { + display: "none", + } + }, + componentProps: { + track, + onSave: (video) => { + console.log("Saving video for track", video) + }, + }, + }) + } + + async function onClose() { + if (typeof props.close === "function") { + props.close() + } + } + + async function onSave() { + await props.onSave(track) + + if (typeof props.close === "function") { + props.close() + } + } + + return
+
+
+ + Cover +
+ + handleChange("cover", url)} + extraActions={[ + + Use Parent + + ]} + /> +
+ +
+
+ + Title +
+ + handleChange("title", e.target.value)} + /> +
+ +
+
+ + Artist +
+ + handleChange("artist", e.target.value)} + /> +
+ +
+
+ + Album +
+ + handleChange("album", e.target.value)} + /> +
+ +
+
+ + Explicit +
+ + handleChange("explicit", value)} + /> +
+ + + +
+
+ + Edit Video +
+ + + Edit + +
+ +
+
+ + Edit Lyrics +
+ + + Edit + +
+ +
+
+ + Timestamps +
+ + + Edit + +
+ +
+ } + onClick={onClose} + > + Cancel + + + } + onClick={onSave} + > + Save + +
+
+} + +export default TrackEditor \ No newline at end of file diff --git a/packages/app/src/components/MusicStudio/TrackEditor/index.less b/packages/app/src/components/MusicStudio/TrackEditor/index.less new file mode 100644 index 00000000..f6570bb8 --- /dev/null +++ b/packages/app/src/components/MusicStudio/TrackEditor/index.less @@ -0,0 +1,57 @@ +.track-editor { + display: flex; + flex-direction: column; + + align-items: center; + + gap: 20px; + + .track-editor-actions { + display: flex; + flex-direction: row; + + align-items: center; + justify-content: flex-end; + + align-self: center; + + gap: 10px; + } + + .track-editor-field { + display: flex; + flex-direction: column; + + align-items: flex-start; + + gap: 10px; + + width: 100%; + + .track-editor-field-header { + display: inline-flex; + flex-direction: row; + + justify-content: flex-start; + align-items: center; + + width: 100%; + + h3 { + font-size: 1.2rem; + } + } + + .track-editor-field-actions { + display: flex; + flex-direction: row; + + align-items: center; + justify-content: center; + + gap: 10px; + + width: 100%; + } + } +} \ No newline at end of file diff --git a/packages/app/src/components/MusicStudio/VideoEditor/index.jsx b/packages/app/src/components/MusicStudio/VideoEditor/index.jsx new file mode 100644 index 00000000..f95f1ef4 --- /dev/null +++ b/packages/app/src/components/MusicStudio/VideoEditor/index.jsx @@ -0,0 +1,14 @@ +import React from "react" +import * as antd from "antd" + +import { Icons } from "@components/Icons" + +import "./index.less" + +const VideoEditor = (props) => { + return
+ +
+} + +export default VideoEditor \ No newline at end of file diff --git a/packages/app/src/components/MusicStudio/VideoEditor/index.less b/packages/app/src/components/MusicStudio/VideoEditor/index.less new file mode 100644 index 00000000..e69de29b diff --git a/packages/app/src/components/PagePanels/index.jsx b/packages/app/src/components/PagePanels/index.jsx index 13bde614..1ce47613 100755 --- a/packages/app/src/components/PagePanels/index.jsx +++ b/packages/app/src/components/PagePanels/index.jsx @@ -8,6 +8,31 @@ import NavMenu from "./components/NavMenu" import "./index.less" +export class Tab extends React.Component { + state = { + error: null + } + + // handle on error + componentDidCatch(err) { + this.setState({ error: err }) + } + + render() { + if (this.state.error) { + return + } + + return <> + {this.props.children} + + } +} + export const Panel = (props) => { return
{ trigger={["click"]} onOpenChange={(open) => { if (open && props.user_id) { - const isSelf = app.cores.permissions.checkUserIdIsSelf(props.user_id) + const isSelf = checkUserIdIsSelf(props.user_id) setIsSelf(isSelf) } diff --git a/packages/app/src/components/PostCard/components/header/index.jsx b/packages/app/src/components/PostCard/components/header/index.jsx index 948b12c6..60d0121a 100755 --- a/packages/app/src/components/PostCard/components/header/index.jsx +++ b/packages/app/src/components/PostCard/components/header/index.jsx @@ -2,6 +2,7 @@ import React from "react" import { DateTime } from "luxon" import { Tag } from "antd" +import TimeAgo from "@components/TimeAgo" import Image from "@components/Image" import { Icons } from "@components/Icons" @@ -10,37 +11,10 @@ import PostReplieView from "@components/PostReplieView" import "./index.less" const PostCardHeader = (props) => { - const [timeAgo, setTimeAgo] = React.useState(0) - const goToProfile = () => { app.navigation.goToAccount(props.postData.user?.username) } - const updateTimeAgo = () => { - let createdAt = props.postData.timestamp ?? props.postData.created_at ?? "" - - const timeAgo = DateTime.fromISO( - createdAt, - { - locale: app.cores.settings.get("language") - } - ).toRelative() - - setTimeAgo(timeAgo) - } - - React.useEffect(() => { - updateTimeAgo() - - const interval = setInterval(() => { - updateTimeAgo() - }, 1000 * 60 * 5) - - return () => { - clearInterval(interval) - } - }, []) - return
{ !props.disableReplyTag && props.postData.reply_to &&
{ - {timeAgo} +
diff --git a/packages/app/src/components/ReleasesList/index.jsx b/packages/app/src/components/ReleasesList/index.jsx new file mode 100644 index 00000000..df46425b --- /dev/null +++ b/packages/app/src/components/ReleasesList/index.jsx @@ -0,0 +1,132 @@ +import React from "react" +import * as antd from "antd" +import { Translation } from "react-i18next" + +import { Icons } from "@components/Icons" +import PlaylistItem from "@components/Music/PlaylistItem" + +import "./index.less" + +const ReleasesList = (props) => { + const hopNumber = props.hopsPerPage ?? 6 + + const [offset, setOffset] = React.useState(0) + const [ended, setEnded] = React.useState(false) + + const [loading, result, error, makeRequest] = app.cores.api.useRequest(props.fetchMethod, { + limit: hopNumber, + trim: offset + }) + + const onClickPrev = () => { + if (offset === 0) { + return + } + + setOffset((value) => { + const newOffset = value - hopNumber + + // check if newOffset is NaN + if (newOffset !== newOffset) { + return false + } + + if (typeof makeRequest === "function") { + makeRequest({ + trim: newOffset, + limit: hopNumber, + }) + } + + return newOffset + }) + } + + const onClickNext = () => { + if (ended) { + return + } + + setOffset((value) => { + const newOffset = value + hopNumber + + // check if newOffset is NaN + if (newOffset !== newOffset) { + return false + } + + if (typeof makeRequest === "function") { + makeRequest({ + trim: newOffset, + limit: hopNumber, + }) + } + + return newOffset + }) + } + + React.useEffect(() => { + if (result) { + if (typeof result.has_more !== "undefined") { + setEnded(!result.has_more) + } else { + setEnded(result.items.length < hopNumber) + } + } + }, [result]) + + if (error) { + console.error(error) + + return
+ +
+ } + + return
+
+

+ { + props.headerIcon + } + + {(t) => t(props.headerTitle)} + +

+ +
+ } + onClick={onClickPrev} + disabled={offset === 0 || loading} + /> + + } + onClick={onClickNext} + disabled={ended || loading} + /> +
+
+
+ { + loading && + } + { + !loading && result.items.map((playlist, index) => { + return + }) + } +
+
+} + +export default ReleasesList \ No newline at end of file diff --git a/packages/app/src/components/ReleasesList/index.less b/packages/app/src/components/ReleasesList/index.less new file mode 100644 index 00000000..7ec79333 --- /dev/null +++ b/packages/app/src/components/ReleasesList/index.less @@ -0,0 +1,52 @@ +.music-releases-list { + display: flex; + flex-direction: column; + + overflow-x: visible; + + .music-releases-list-header { + display: flex; + flex-direction: row; + + align-items: center; + + margin-bottom: 20px; + + h1 { + font-size: 1.5rem; + margin: 0; + } + + .music-releases-list-actions { + display: flex; + flex-direction: row; + + gap: 10px; + + align-self: center; + + margin-left: auto; + } + } + + .music-releases-list-items { + display: grid; + + grid-gap: 20px; + grid-template-columns: repeat(3, minmax(0, 1fr)); + + min-width: 372px !important; + + @media (min-width: 2000px) { + grid-template-columns: repeat(4, 1fr); + } + + @media (min-width: 2300px) { + grid-template-columns: repeat(5, 1fr); + } + + .playlistItem { + justify-self: center; + } + } +} \ No newline at end of file diff --git a/packages/app/src/components/TimeAgo/index.jsx b/packages/app/src/components/TimeAgo/index.jsx new file mode 100644 index 00000000..0d446b88 --- /dev/null +++ b/packages/app/src/components/TimeAgo/index.jsx @@ -0,0 +1,33 @@ +import React from "react" + +import { DateTime } from "luxon" + +const TimeAgo = (props) => { + const [calculationInterval, setCalculationInterval] = React.useState(null) + const [text, setText] = React.useState("") + + async function calculateRelative() { + const timeAgo = DateTime.fromISO( + props.time, + { + locale: app.cores.settings.get("language") + } + ).toRelative() + + setText(timeAgo) + } + + React.useEffect(() => { + setCalculationInterval(setInterval(calculateRelative, props.interval ?? 3000)) + + calculateRelative() + + return () => { + clearInterval(calculationInterval) + } + }, []) + + return text +} + +export default TimeAgo \ No newline at end of file diff --git a/packages/app/src/components/UserCard/index.jsx b/packages/app/src/components/UserCard/index.jsx index 9a5daf23..dfebc520 100755 --- a/packages/app/src/components/UserCard/index.jsx +++ b/packages/app/src/components/UserCard/index.jsx @@ -54,7 +54,7 @@ const UserLink = (props) => { const handleOnClick = () => { if (!hasHref) { if (app.isMobile) { - app.DrawerController.open("link_viewer", UserLinkViewer, { + app.layout.drawer.open("link_viewer", UserLinkViewer, { componentProps: { link: link, decorator: decorator diff --git a/packages/app/src/cores/api/api.core.js b/packages/app/src/cores/api/api.core.js index c791ef4a..d3f4d8e1 100755 --- a/packages/app/src/cores/api/api.core.js +++ b/packages/app/src/cores/api/api.core.js @@ -30,7 +30,7 @@ export default class APICore extends Core { listenEvent(key, handler, instance = "default") { if (!this.client.sockets[instance]) { - console.error(`[API] Websocket instance ${instance} not found`) + this.console.error(`[API] Websocket instance ${instance} not found`) return false } @@ -40,7 +40,7 @@ export default class APICore extends Core { unlistenEvent(key, handler, instance = "default") { if (!this.client.sockets[instance]) { - console.error(`[API] Websocket instance ${instance} not found`) + this.console.error(`[API] Websocket instance ${instance} not found`) return false } diff --git a/packages/app/src/cores/nfc/nfc.core.js b/packages/app/src/cores/nfc/nfc.core.js index a8bb15ab..6d274157 100755 --- a/packages/app/src/cores/nfc/nfc.core.js +++ b/packages/app/src/cores/nfc/nfc.core.js @@ -161,7 +161,7 @@ export default class NFC extends Core { if (this.subscribers.length === 0) { if (tag.message.records?.length > 0) { // open dialog - app.DrawerController.open("nfc_card_dialog", TapShareDialog, { + app.layout.drawer.open("nfc_card_dialog", TapShareDialog, { componentProps: { tag: tag, } @@ -187,7 +187,7 @@ export default class NFC extends Core { if (this.subscribers.length === 0 && tag.message?.records) { if (tag.message.records?.length > 0) { // open dialog - app.DrawerController.open("nfc_card_dialog", TapShareDialog, { + app.layout.drawer.open("nfc_card_dialog", TapShareDialog, { componentProps: { tag: tag, } diff --git a/packages/app/src/cores/permissions/permissions.core.js b/packages/app/src/cores/permissions/permissions.core.js deleted file mode 100755 index e2fbae17..00000000 --- a/packages/app/src/cores/permissions/permissions.core.js +++ /dev/null @@ -1,61 +0,0 @@ -import Core from "evite/src/core" - -import UserModel from "@models/user" -import SessionModel from "@models/session" - -export default class PermissionsCore extends Core { - static namespace = "permissions" - - static dependencies = ["api"] - - public = { - getRoles: this.getRoles, - hasAdmin: this.hasAdmin, - checkUserIdIsSelf: this.checkUserIdIsSelf, - hasPermission: this.hasPermission, - } - - async hasAdmin() { - return await UserModel.haveAdmin() - } - - checkUserIdIsSelf(user_id) { - return SessionModel.user_id === user_id - } - - async getRoles() { - return await UserModel.selfRoles() - } - - async hasPermission(permission, adminPreference = false) { - if (adminPreference) { - const admin = await this.hasAdmin() - - if (admin) { - return true - } - } - - let query = [] - - if (Array.isArray(permission)) { - query = permission - } else { - query = [permission] - } - - // create a promise and check if the user has all the permission in the query - const result = await Promise.all(query.map(async (permission) => { - const hasPermission = await UserModel.haveRole(permission) - - return hasPermission - })) - - // if the user has all the permission in the query, return true - if (result.every((hasPermission) => hasPermission)) { - return true - } - - return false - } -} \ No newline at end of file diff --git a/packages/app/src/cores/player/classes/TrackInstance.js b/packages/app/src/cores/player/classes/TrackInstance.js new file mode 100644 index 00000000..7915308e --- /dev/null +++ b/packages/app/src/cores/player/classes/TrackInstance.js @@ -0,0 +1,127 @@ +export default class TrackInstance { + constructor(player, manifest) { + if (!player) { + throw new Error("Player core is required") + } + + if (typeof manifest === "undefined") { + throw new Error("Manifest is required") + } + + this.player = player + this.manifest = manifest + + return this + } + + audio = null + + contextElement = null + + abortController = new AbortController() + + attachedProcessors = [] + + waitUpdateTimeout = null + + resolveManifest = async () => { + if (typeof this.manifest === "string") { + this.manifest = { + src: this.manifest, + } + } + + if (this.manifest.service) { + if (!this.player.service_providers.has(manifest.service)) { + throw new Error(`Service ${manifest.service} is not supported`) + } + + // try to resolve source file + if (this.manifest.service !== "inherit" && !this.manifest.source) { + this.manifest = await this.player.service_providers.resolve(this.manifest.service, this.manifest) + } + } + + if (!this.manifest.source) { + throw new Error("Manifest `source` is required") + } + + if (!this.manifest.metadata) { + this.manifest.metadata = {} + } + + if (!this.manifest.metadata.title) { + this.manifest.metadata.title = this.manifest.source.split("/").pop() + } + + return this.manifest + } + + initialize = async () => { + this.manifest = await this.resolveManifest() + + this.audio = new Audio(this.manifest.source) + + this.audio.signal = this.abortController.signal + this.audio.crossOrigin = "anonymous" + this.audio.preload = "metadata" + + for (const [key, value] of Object.entries(this.mediaEvents)) { + this.audio.addEventListener(key, value) + } + + this.contextElement = this.player.audioContext.createMediaElementSource(this.audio) + + return this + } + + mediaEvents = { + "ended": () => { + this.player.next() + }, + "loadeddata": () => { + this.player.state.loading = false + }, + "loadedmetadata": () => { + // TODO: Detect a livestream and change mode + // if (instance.media.duration === Infinity) { + // instance.manifest.stream = true + + // this.state.livestream_mode = true + // } + }, + "play": () => { + this.player.state.playback_status = "playing" + }, + "playing": () => { + this.player.state.loading = false + + this.player.state.playback_status = "playing" + + if (typeof this.waitUpdateTimeout !== "undefined") { + clearTimeout(this.waitUpdateTimeout) + this.waitUpdateTimeout = null + } + }, + "pause": () => { + this.player.state.playback_status = "paused" + }, + // "durationchange": (duration) => { + + // }, + "waiting": () => { + if (this.waitUpdateTimeout) { + clearTimeout(this.waitUpdateTimeout) + this.waitUpdateTimeout = null + } + + // if takes more than 150ms to load, update loading state + this.waitUpdateTimeout = setTimeout(() => { + this.player.state.loading = true + }, 150) + }, + "seeked": () => { + this.player.eventBus.emit(`player.seeked`, this.audio.currentTime) + }, + } +} \ No newline at end of file diff --git a/packages/app/src/cores/player/player.bkp.js b/packages/app/src/cores/player/player.bkp.js new file mode 100755 index 00000000..356bbe17 --- /dev/null +++ b/packages/app/src/cores/player/player.bkp.js @@ -0,0 +1,947 @@ +import Core from "evite/src/core" +import EventEmitter from "evite/src/internals/EventEmitter" +import { Observable } from "object-observer" +import { FastAverageColor } from "fast-average-color" + +import MusicModel from "comty.js/models/music" + +import ToolBarPlayer from "@components/Player/ToolBarPlayer" +import BackgroundMediaPlayer from "@components/Player/BackgroundMediaPlayer" + +import AudioPlayerStorage from "./player.storage" + +import defaultAudioProccessors from "./processors" + +import MediaSession from "./mediaSession" +import ServiceProviders from "./services" + +export default class Player extends Core { + static dependencies = [ + "api", + "settings" + ] + + static namespace = "player" + + static bgColor = "aquamarine" + static textColor = "black" + + static defaultSampleRate = 48000 + + static gradualFadeMs = 150 + + // buffer & precomputation + static maxManifestPrecompute = 3 + + service_providers = new ServiceProviders() + + native_controls = new MediaSession() + + currentDomWindow = null + + audioContext = new AudioContext({ + sampleRate: AudioPlayerStorage.get("sample_rate") ?? Player.defaultSampleRate, + latencyHint: "playback" + }) + + audioProcessors = [] + + eventBus = new EventEmitter() + + fac = new FastAverageColor() + + track_prev_instances = [] + track_instance = null + track_next_instances = [] + + state = Observable.from({ + loading: false, + minimized: false, + + muted: app.isMobile ? false : (AudioPlayerStorage.get("mute") ?? false), + volume: app.isMobile ? 1 : (AudioPlayerStorage.get("volume") ?? 0.3), + + sync_mode: false, + livestream_mode: false, + control_locked: false, + + track_manifest: null, + + playback_mode: AudioPlayerStorage.get("mode") ?? "normal", + playback_status: "stopped", + }) + + public = { + audioContext: this.audioContext, + setSampleRate: this.setSampleRate, + start: this.start.bind(this), + close: this.close.bind(this), + playback: { + mode: this.playbackMode.bind(this), + stop: this.stop.bind(this), + toggle: this.togglePlayback.bind(this), + pause: this.pausePlayback.bind(this), + play: this.resumePlayback.bind(this), + next: this.next.bind(this), + previous: this.previous.bind(this), + seek: this.seek.bind(this), + }, + _setLoading: function (to) { + this.state.loading = !!to + }.bind(this), + duration: this.duration.bind(this), + volume: this.volume.bind(this), + mute: this.mute.bind(this), + toggleMute: this.toggleMute.bind(this), + seek: this.seek.bind(this), + minimize: this.toggleMinimize.bind(this), + collapse: this.toggleCollapse.bind(this), + state: new Proxy(this.state, { + get: (target, prop) => { + return target[prop] + }, + set: (target, prop, value) => { + return false + } + }), + eventBus: new Proxy(this.eventBus, { + get: (target, prop) => { + return target[prop] + }, + set: (target, prop, value) => { + return false + } + }), + gradualFadeMs: Player.gradualFadeMs, + trackInstance: () => { + return this.track_instance + } + } + + internalEvents = { + "player.state.update:loading": () => { + //app.cores.sync.music.dispatchEvent("music.player.state.update", this.state) + }, + "player.state.update:track_manifest": () => { + //app.cores.sync.music.dispatchEvent("music.player.state.update", this.state) + }, + "player.state.update:playback_status": () => { + //app.cores.sync.music.dispatchEvent("music.player.state.update", this.state) + }, + "player.seeked": (to) => { + //app.cores.sync.music.dispatchEvent("music.player.seek", to) + }, + } + + async onInitialize() { + this.native_controls.initialize() + + this.initializeAudioProcessors() + + for (const [eventName, eventHandler] of Object.entries(this.internalEvents)) { + this.eventBus.on(eventName, eventHandler) + } + + Observable.observe(this.state, async (changes) => { + try { + changes.forEach((change) => { + if (change.type === "update") { + const stateKey = change.path[0] + + this.eventBus.emit(`player.state.update:${stateKey}`, change.object[stateKey]) + this.eventBus.emit("player.state.update", change.object) + } + }) + } catch (error) { + this.console.error(`Failed to dispatch state updater >`, error) + } + }) + } + + async initializeBeforeRuntimeInitialize() { + for (const [eventName, eventHandler] of Object.entries(this.wsEvents)) { + app.cores.api.listenEvent(eventName, eventHandler, Player.websocketListen) + } + + if (app.isMobile) { + this.state.audioVolume = 1 + } + } + + async initializeAudioProcessors() { + if (this.audioProcessors.length > 0) { + this.console.log("Destroying audio processors") + + this.audioProcessors.forEach((processor) => { + this.console.log(`Destroying audio processor ${processor.constructor.name}`, processor) + processor._destroy() + }) + + this.audioProcessors = [] + } + + for await (const defaultProccessor of defaultAudioProccessors) { + this.audioProcessors.push(new defaultProccessor(this)) + } + + for await (const processor of this.audioProcessors) { + if (typeof processor._init === "function") { + try { + await processor._init(this.audioContext) + } catch (error) { + this.console.error(`Failed to initialize audio processor ${processor.constructor.name} >`, error) + continue + } + } + + // check if processor has exposed public methods + if (processor.exposeToPublic) { + Object.entries(processor.exposeToPublic).forEach(([key, value]) => { + const refName = processor.constructor.refName + + if (typeof this.public[refName] === "undefined") { + // by default create a empty object + this.public[refName] = {} + } + + this.public[refName][key] = value + }) + } + } + } + + // + // UI Methods + // + + attachPlayerComponent() { + if (this.currentDomWindow) { + this.console.warn("EmbbededMediaPlayer already attached") + return false + } + + if (app.layout.tools_bar) { + this.currentDomWindow = app.layout.tools_bar.attachRender("mediaPlayer", ToolBarPlayer) + } + + } + + detachPlayerComponent() { + if (!this.currentDomWindow) { + this.console.warn("EmbbededMediaPlayer not attached") + return false + } + + if (!app.layout.tools_bar) { + this.console.error("Tools bar not found") + return false + } + + app.layout.tools_bar.detachRender("mediaPlayer") + + this.currentDomWindow = null + } + + // + // Instance managing methods + // + async abortPreloads() { + for await (const instance of this.track_next_instances) { + if (instance.abortController?.abort) { + instance.abortController.abort() + } + } + } + + async preloadAudioInstance(instance) { + const isIndex = typeof instance === "number" + + let index = isIndex ? instance : 0 + + if (isIndex) { + instance = this.track_next_instances[instance] + } + + if (!instance) { + this.console.error("Instance not found to preload") + return false + } + + if (!instance.manifest.cover_analysis) { + const cover_analysis = await this.fac.getColorAsync(`https://corsproxy.io/?${encodeURIComponent(instance.manifest.cover ?? instance.manifest.thumbnail)}`) + .catch((err) => { + this.console.error(err) + + return false + }) + + instance.manifest.cover_analysis = cover_analysis + } + + if (!instance._preloaded) { + instance.media.preload = "metadata" + instance._preloaded = true + } + + if (isIndex) { + this.track_next_instances[index] = instance + } + + return instance + } + + async destroyCurrentInstance({ sync = false } = {}) { + if (!this.track_instance) { + return false + } + + // stop playback + if (this.track_instance.media) { + this.track_instance.media.pause() + } + + // reset track_instance + this.track_instance = null + + // reset livestream mode + this.state.livestream_mode = false + } + + async createInstance(manifest) { + if (!manifest) { + this.console.error("Manifest is required") + return false + } + + if (typeof manifest === "string") { + manifest = { + src: manifest, + } + } + + // check if manifest has `manifest` property, if is and not inherit or missing source, resolve + if (manifest.service) { + if (!this.service_providers.has(manifest.service)) { + this.console.error(`Service ${manifest.service} is not supported`) + return false + } + + if (manifest.service !== "inherit" && !manifest.source) { + manifest = await this.service_providers.resolve(manifest.service, manifest) + } + } + + if (!manifest.src && !manifest.source) { + this.console.error("Manifest source is required") + return false + } + + const source = manifest.src ?? manifest.source + + if (!manifest.metadata) { + manifest.metadata = {} + } + + // if title is not set, use the audio source filename + if (!manifest.metadata.title) { + manifest.metadata.title = source.split("/").pop() + } + + let instance = { + manifest: manifest, + attachedProcessors: [], + abortController: new AbortController(), + source: source, + media: new Audio(source), + duration: null, + seek: 0, + track: null, + } + + instance.media.signal = instance.abortController.signal + instance.media.crossOrigin = "anonymous" + instance.media.preload = "metadata" + + instance.media.loop = this.state.playback_mode === "repeat" + instance.media.volume = this.state.volume + + // handle on end + instance.media.addEventListener("ended", () => { + this.next() + }) + + instance.media.addEventListener("loadeddata", () => { + this.state.loading = false + }) + + // update playback status + instance.media.addEventListener("play", () => { + this.state.playback_status = "playing" + }) + + instance.media.addEventListener("playing", () => { + this.state.loading = false + + this.state.playback_status = "playing" + + if (this.waitUpdateTimeout) { + clearTimeout(this.waitUpdateTimeout) + this.waitUpdateTimeout = null + } + }) + + instance.media.addEventListener("pause", () => { + this.state.playback_status = "paused" + }) + + instance.media.addEventListener("durationchange", (duration) => { + if (instance.media.paused) { + return false + } + + instance.duration = duration + }) + + instance.media.addEventListener("waiting", () => { + if (instance.media.paused) { + return false + } + + if (this.waitUpdateTimeout) { + clearTimeout(this.waitUpdateTimeout) + this.waitUpdateTimeout = null + } + + // if takes more than 150ms to load, update loading state + this.waitUpdateTimeout = setTimeout(() => { + this.state.loading = true + }, 150) + }) + + instance.media.addEventListener("seeked", () => { + this.console.log(`Seeked to ${instance.seek}`) + + this.eventBus.emit(`player.seeked`, instance.seek) + }) + + instance.media.addEventListener("loadedmetadata", () => { + if (instance.media.duration === Infinity) { + instance.manifest.stream = true + + this.state.livestream_mode = true + } + }, { once: true }) + + instance.track = this.audioContext.createMediaElementSource(instance.media) + + return instance + } + + async attachProcessorsToInstance(instance) { + for await (const [index, processor] of this.audioProcessors.entries()) { + if (processor.constructor.node_bypass === true) { + instance.track.connect(processor.processor) + + processor.processor.connect(this.audioContext.destination) + + continue + } + + if (typeof processor._attach !== "function") { + this.console.error(`Processor ${processor.constructor.refName} not support attach`) + + continue + } + + instance = await processor._attach(instance, index) + } + + const lastProcessor = instance.attachedProcessors[instance.attachedProcessors.length - 1].processor + + // now attach to destination + lastProcessor.connect(this.audioContext.destination) + + return instance + } + + // + // Playback methods + // + async play(instance, params = {}) { + if (typeof instance === "number") { + if (instance < 0) { + instance = this.track_prev_instances[instance] + } + + if (instance > 0) { + instance = this.track_instances[instance] + } + + if (instance === 0) { + instance = this.track_instance + } + } + + if (!instance) { + throw new Error("Audio instance is required") + } + + if (this.audioContext.state === "suspended") { + this.audioContext.resume() + } + + if (this.track_instance) { + this.track_instance = this.track_instance.attachedProcessors[this.track_instance.attachedProcessors.length - 1]._destroy(this.track_instance) + + this.destroyCurrentInstance() + } + + // attach processors + instance = await this.attachProcessorsToInstance(instance) + + // now set the current instance + this.track_instance = await this.preloadAudioInstance(instance) + + // reconstruct audio src if is not set + if (this.track_instance.media.src !== instance.source) { + this.track_instance.media.src = instance.source + } + + // set time to 0 + this.track_instance.media.currentTime = 0 + + if (params.time >= 0) { + this.track_instance.media.currentTime = params.time + } + + this.track_instance.media.muted = this.state.muted + this.track_instance.media.loop = this.state.playback_mode === "repeat" + + // try to preload next audio + // TODO: Use a better way to preload queues + if (this.track_next_instances.length > 0) { + this.preloadAudioInstance(1) + } + + // play + await this.track_instance.media.play() + + this.console.debug(`Playing track >`, this.track_instance) + + // update manifest + this.state.track_manifest = instance.manifest + + this.native_controls.update(instance.manifest) + + return this.track_instance + } + + async start(manifest, { sync = false, time, startIndex = 0 } = {}) { + if (this.state.control_locked && !sync) { + this.console.warn("Controls are locked, cannot do this action") + return false + } + + this.attachPlayerComponent() + + // !IMPORTANT: abort preloads before destroying current instance + await this.abortPreloads() + await this.destroyCurrentInstance({ + sync + }) + + this.state.loading = true + + this.track_prev_instances = [] + this.track_next_instances = [] + + let playlist = Array.isArray(manifest) ? manifest : [manifest] + + if (playlist.length === 0) { + this.console.warn(`[PLAYER] Playlist is empty, aborting...`) + return false + } + + if (playlist.some((item) => typeof item === "string")) { + playlist = await this.service_providers.resolveMany(playlist) + } + + playlist = playlist.slice(startIndex) + + for await (const [index, _manifest] of playlist.entries()) { + const instance = await this.createInstance(_manifest) + + this.track_next_instances.push(instance) + + if (index === 0) { + this.play(this.track_next_instances[0], { + time: time ?? 0 + }) + } + } + + return manifest + } + + next({ sync = false } = {}) { + if (this.state.control_locked && !sync) { + //this.console.warn("Sync mode is locked, cannot do this action") + return false + } + + if (this.track_next_instances.length > 0) { + // move current audio instance to history + this.track_prev_instances.push(this.track_next_instances.shift()) + } + + if (this.track_next_instances.length === 0) { + this.console.log(`[PLAYER] No more tracks to play, stopping...`) + + return this.stop() + } + + let nextIndex = 0 + + if (this.state.playback_mode === "shuffle") { + nextIndex = Math.floor(Math.random() * this.track_next_instances.length) + } + + this.play(this.track_next_instances[nextIndex]) + } + + previous({ sync = false } = {}) { + if (this.state.control_locked && !sync) { + //this.console.warn("Sync mode is locked, cannot do this action") + return false + } + + if (this.track_prev_instances.length > 0) { + // move current audio instance to history + this.track_next_instances.unshift(this.track_prev_instances.pop()) + + return this.play(this.track_next_instances[0]) + } + + if (this.track_prev_instances.length === 0) { + this.console.log(`[PLAYER] No previous tracks, replying...`) + // replay the current track + return this.play(this.track_instance) + } + } + + async togglePlayback() { + if (this.state.playback_status === "paused") { + await this.resumePlayback() + } else { + await this.pausePlayback() + } + } + + async pausePlayback() { + return await new Promise((resolve, reject) => { + if (!this.track_instance) { + this.console.error("No audio instance") + return null + } + + // set gain exponentially + this.track_instance.gainNode.gain.linearRampToValueAtTime( + 0.0001, + this.audioContext.currentTime + (Player.gradualFadeMs / 1000) + ) + + setTimeout(() => { + this.track_instance.media.pause() + resolve() + }, Player.gradualFadeMs) + + this.native_controls.updateIsPlaying(false) + }) + } + + async resumePlayback() { + if (!this.state.playback_status === "playing") { + return true + } + + return await new Promise((resolve, reject) => { + if (!this.track_instance) { + this.console.error("No audio instance") + return null + } + + // ensure audio elemeto starts from 0 volume + this.track_instance.gainNode.gain.value = 0.0001 + + this.track_instance.media.play().then(() => { + resolve() + }) + + // set gain exponentially + this.track_instance.gainNode.gain.linearRampToValueAtTime( + this.state.volume, + this.audioContext.currentTime + (Player.gradualFadeMs / 1000) + ) + + this.native_controls.updateIsPlaying(true) + }) + } + + stop() { + this.destroyCurrentInstance() + this.abortPreloads() + + this.state.playback_status = "stopped" + this.state.track_manifest = null + + this.state.livestream_mode = false + + this.track_instance = null + this.track_next_instances = [] + this.track_prev_instances = [] + + this.native_controls.destroy() + } + + mute(to) { + if (app.isMobile && typeof to !== "boolean") { + this.console.warn("Cannot mute on mobile") + return false + } + + if (typeof to === "boolean") { + this.state.muted = to + this.track_instance.media.muted = to + } + + return this.state.muted + } + + volume(volume) { + if (typeof volume !== "number") { + return this.state.volume + } + + if (app.isMobile) { + this.console.warn("Cannot change volume on mobile") + return false + } + + if (volume > 1) { + if (!app.cores.settings.get("player.allowVolumeOver100")) { + volume = 1 + } + } + + if (volume < 0) { + volume = 0 + } + + this.state.volume = volume + + AudioPlayerStorage.set("volume", volume) + + if (this.track_instance) { + if (this.track_instance.gainNode) { + this.track_instance.gainNode.gain.value = this.state.volume + } + } + + return this.state.volume + } + + seek(time, { sync = false } = {}) { + if (!this.track_instance || !this.track_instance.media) { + return false + } + + // if time not provided, return current time + if (typeof time === "undefined") { + return this.track_instance.media.currentTime + } + + if (this.state.control_locked && !sync) { + this.console.warn("Sync mode is locked, cannot do this action") + return false + } + + + // if time is provided, seek to that time + if (typeof time === "number") { + this.console.log(`Seeking to ${time} | Duration: ${this.track_instance.media.duration}`) + + this.track_instance.media.currentTime = time + + return time + } + } + + playbackMode(mode) { + if (typeof mode !== "string") { + return this.state.playback_mode + } + + this.state.playback_mode = mode + + if (this.track_instance) { + this.track_instance.media.loop = this.state.playback_mode === "repeat" + } + + AudioPlayerStorage.set("mode", mode) + + return mode + } + + duration() { + if (!this.track_instance) { + return false + } + + return this.track_instance.media.duration + } + + loop(to) { + if (typeof to !== "boolean") { + this.console.warn("Loop must be a boolean") + return false + } + + this.state.loop = to ?? !this.state.loop + + if (this.track_instance.media) { + this.track_instance.media.loop = this.state.loop + } + + return this.state.loop + } + + close() { + this.stop() + this.detachPlayerComponent() + } + + toggleMinimize(to) { + this.state.minimized = to ?? !this.state.minimized + + if (this.state.minimized) { + app.layout.sidebar.attachBottomItem("player", BackgroundMediaPlayer, { + noContainer: true + }) + } else { + app.layout.sidebar.removeBottomItem("player") + } + + return this.state.minimized + } + + toggleCollapse(to) { + if (typeof to !== "boolean") { + this.console.warn("Collapse must be a boolean") + return false + } + + this.state.collapsed = to ?? !this.state.collapsed + + return this.state.collapsed + } + + toggleSyncMode(to, lock) { + if (typeof to !== "boolean") { + this.console.warn("Sync mode must be a boolean") + return false + } + + this.state.syncMode = to ?? !this.state.syncMode + + this.state.syncModeLocked = lock ?? false + + this.console.log(`Sync mode is now ${this.state.syncMode ? "enabled" : "disabled"} | Locked: ${this.state.syncModeLocked ? "yes" : "no"}`) + + return this.state.syncMode + } + + toggleMute(to) { + if (typeof to !== "boolean") { + to = !this.state.muted + } + + return this.mute(to) + } + + async getTracksByIds(list) { + if (!Array.isArray(list)) { + this.console.warn("List must be an array") + return false + } + + let ids = [] + + list.forEach((item) => { + if (typeof item === "string") { + ids.push(item) + } + }) + + if (ids.length === 0) { + return list + } + + const fetchedTracks = await MusicModel.getTracksData(ids).catch((err) => { + this.console.error(err) + return false + }) + + if (!fetchedTracks) { + return list + } + + // replace fetched tracks with the ones in the list + fetchedTracks.forEach((fetchedTrack) => { + const index = list.findIndex((item) => item === fetchedTrack._id) + + if (index !== -1) { + list[index] = fetchedTrack + } + }) + + return list + } + + async setSampleRate(to) { + // must be a integer + if (typeof to !== "number") { + this.console.error("Sample rate must be a number") + return this.audioContext.sampleRate + } + + // must be a integer + if (!Number.isInteger(to)) { + this.console.error("Sample rate must be a integer") + return this.audioContext.sampleRate + } + + return await new Promise((resolve, reject) => { + app.confirm({ + title: "Change sample rate", + content: `To change the sample rate, the app needs to be reloaded. Do you want to continue?`, + onOk: () => { + try { + this.audioContext = new AudioContext({ sampleRate: to }) + + AudioPlayerStorage.set("sample_rate", to) + + app.navigation.reload() + + return resolve(this.audioContext.sampleRate) + } catch (error) { + app.message.error(`Failed to change sample rate, ${error.message}`) + return resolve(this.audioContext.sampleRate) + } + }, + onCancel: () => { + return resolve(this.audioContext.sampleRate) + } + }) + }) + } +} \ No newline at end of file diff --git a/packages/app/src/cores/player/player.core.js b/packages/app/src/cores/player/player.core.js index 9eeb8115..5fe57d79 100755 --- a/packages/app/src/cores/player/player.core.js +++ b/packages/app/src/cores/player/player.core.js @@ -3,17 +3,16 @@ import EventEmitter from "evite/src/internals/EventEmitter" import { Observable } from "object-observer" import { FastAverageColor } from "fast-average-color" -import MusicModel from "comty.js/models/music" - import ToolBarPlayer from "@components/Player/ToolBarPlayer" import BackgroundMediaPlayer from "@components/Player/BackgroundMediaPlayer" import AudioPlayerStorage from "./player.storage" +import TrackInstanceClass from "./classes/TrackInstance" import defaultAudioProccessors from "./processors" import MediaSession from "./mediaSession" -import ServicesHandlers from "./services" +import ServiceProviders from "./services" export default class Player extends Core { static dependencies = [ @@ -33,6 +32,8 @@ export default class Player extends Core { // buffer & precomputation static maxManifestPrecompute = 3 + service_providers = new ServiceProviders() + native_controls = new MediaSession() currentDomWindow = null @@ -94,7 +95,6 @@ export default class Player extends Core { seek: this.seek.bind(this), minimize: this.toggleMinimize.bind(this), collapse: this.toggleCollapse.bind(this), - toggleCurrentTrackLike: this.toggleCurrentTrackLike.bind(this), state: new Proxy(this.state, { get: (target, prop) => { return target[prop] @@ -112,6 +112,9 @@ export default class Player extends Core { } }), gradualFadeMs: Player.gradualFadeMs, + trackInstance: () => { + return this.track_instance + } } internalEvents = { @@ -181,8 +184,6 @@ export default class Player extends Core { } for await (const processor of this.audioProcessors) { - this.console.log(`Initializing audio processor ${processor.constructor.name}`, processor) - if (typeof processor._init === "function") { try { await processor._init(this.audioContext) @@ -277,7 +278,7 @@ export default class Player extends Core { } if (!instance._preloaded) { - instance.media.preload = "metadata" + instance.audio.preload = "metadata" instance._preloaded = true } @@ -294,8 +295,8 @@ export default class Player extends Core { } // stop playback - if (this.track_instance.media) { - this.track_instance.media.pause() + if (this.track_instance.audio) { + this.track_instance.audio.pause() } // reset track_instance @@ -305,154 +306,10 @@ export default class Player extends Core { this.state.livestream_mode = false } - async createInstance(manifest) { - if (!manifest) { - this.console.error("Manifest is required") - return false - } - - if (typeof manifest === "string") { - manifest = { - src: manifest, - } - } - - // check if manifest has `manifest` property, if is and not inherit or missing source, resolve - if (manifest.service) { - if (!ServicesHandlers[manifest.service]) { - this.console.error(`Service ${manifest.service} is not supported`) - return false - } - - if (manifest.service !== "inherit" && !manifest.source) { - const resolver = ServicesHandlers[manifest.service].resolve - - if (!resolver) { - this.console.error(`Resolving for service [${manifest.service}] is not supported`) - return false - } - - manifest = await resolver(manifest) - } - } - - if (!manifest.src && !manifest.source) { - this.console.error("Manifest source is required") - return false - } - - const source = manifest.src ?? manifest.source - - if (!manifest.metadata) { - manifest.metadata = {} - } - - // if title is not set, use the audio source filename - if (!manifest.metadata.title) { - manifest.metadata.title = source.split("/").pop() - } - - let instance = { - manifest: manifest, - attachedProcessors: [], - abortController: new AbortController(), - source: source, - media: new Audio(source), - duration: null, - seek: 0, - track: null, - } - - instance.media.signal = instance.abortController.signal - instance.media.crossOrigin = "anonymous" - instance.media.preload = "none" - - instance.media.loop = this.state.playback_mode === "repeat" - instance.media.volume = this.state.volume - - // handle on end - instance.media.addEventListener("ended", () => { - this.next() - }) - - instance.media.addEventListener("loadeddata", () => { - this.state.loading = false - }) - - // update playback status - instance.media.addEventListener("play", () => { - this.state.playback_status = "playing" - }) - - instance.media.addEventListener("playing", () => { - this.state.loading = false - - this.state.playback_status = "playing" - - if (this.waitUpdateTimeout) { - clearTimeout(this.waitUpdateTimeout) - this.waitUpdateTimeout = null - } - }) - - instance.media.addEventListener("pause", () => { - this.state.playback_status = "paused" - }) - - instance.media.addEventListener("durationchange", (duration) => { - if (instance.media.paused) { - return false - } - - instance.duration = duration - }) - - instance.media.addEventListener("waiting", () => { - if (instance.media.paused) { - return false - } - - if (this.waitUpdateTimeout) { - clearTimeout(this.waitUpdateTimeout) - this.waitUpdateTimeout = null - } - - // if takes more than 150ms to load, update loading state - this.waitUpdateTimeout = setTimeout(() => { - this.state.loading = true - }, 150) - }) - - instance.media.addEventListener("seeked", () => { - instance.seek = instance.media.currentTime - - if (this.state.sync_mode) { - // useMusicSync("music:player:seek", { - // position: instance.seek, - // state: this.state, - // }) - } - - this.eventBus.emit(`player.seeked`, instance.seek) - }) - - instance.media.addEventListener("loadedmetadata", () => { - if (instance.media.duration === Infinity) { - instance.manifest.stream = true - - this.state.livestream_mode = true - } - }, { once: true }) - - instance.track = this.audioContext.createMediaElementSource(instance.media) - - return instance - } - async attachProcessorsToInstance(instance) { for await (const [index, processor] of this.audioProcessors.entries()) { if (processor.constructor.node_bypass === true) { - instance.track.connect(processor.processor) + instance.contextElement.connect(processor.processor) processor.processor.connect(this.audioContext.destination) @@ -515,35 +372,32 @@ export default class Player extends Core { this.track_instance = await this.preloadAudioInstance(instance) // reconstruct audio src if is not set - if (this.track_instance.media.src !== instance.source) { - this.track_instance.media.src = instance.source + if (this.track_instance.audio.src !== instance.manifest.source) { + this.track_instance.audio.src = instance.manifest.source } // set time to 0 - this.track_instance.media.currentTime = 0 + this.track_instance.audio.currentTime = 0 if (params.time >= 0) { - this.track_instance.media.currentTime = params.time + this.track_instance.audio.currentTime = params.time } - if (params.volume >= 0) { - this.track_instance.gainNode.gain.value = params.volume - } else { - this.track_instance.gainNode.gain.value = this.state.volume - } - - this.track_instance.media.muted = this.state.muted - this.track_instance.media.loop = this.state.playback_mode === "repeat" + this.track_instance.audio.muted = this.state.muted + this.track_instance.audio.loop = this.state.playback_mode === "repeat" + + this.track_instance.gainNode.gain.value = this.state.volume // try to preload next audio + // TODO: Use a better way to preload queues if (this.track_next_instances.length > 0) { this.preloadAudioInstance(1) } // play - await this.track_instance.media.play() + await this.track_instance.audio.play() - this.console.log(this.track_instance) + this.console.debug(`Playing track >`, this.track_instance) // update manifest this.state.track_manifest = instance.manifest @@ -572,45 +426,31 @@ export default class Player extends Core { this.track_prev_instances = [] this.track_next_instances = [] - const isPlaylist = Array.isArray(manifest) + let playlist = Array.isArray(manifest) ? manifest : [manifest] - if (isPlaylist) { - let playlist = manifest - - if (playlist.length === 0) { - this.console.warn(`[PLAYER] Playlist is empty, aborting...`) - return false - } - - if (playlist.some((item) => typeof item === "string")) { - this.console.log("Resolving missing manifests by ids...") - playlist = await ServicesHandlers.default.resolveMany(playlist) - } - - playlist = playlist.slice(startIndex) - - for await (const [index, _manifest] of playlist.entries()) { - const instance = await this.createInstance(_manifest) - - this.track_next_instances.push(instance) - - if (index === 0) { - this.play(this.track_next_instances[0], { - time: time ?? 0 - }) - } - } - - return playlist + if (playlist.length === 0) { + this.console.warn(`Playlist is empty, aborting...`) + return false } - const instance = await this.createInstance(manifest) + if (playlist.some((item) => typeof item === "string")) { + playlist = await this.service_providers.resolveMany(playlist) + } - this.track_next_instances.push(instance) + playlist = playlist.slice(startIndex) - this.play(this.track_next_instances[0], { - time: time ?? 0 - }) + for await (const [index, _manifest] of playlist.entries()) { + let instance = new TrackInstanceClass(this, _manifest) + instance = await instance.initialize() + + this.track_next_instances.push(instance) + + if (index === 0) { + this.play(this.track_next_instances[0], { + time: time ?? 0 + }) + } + } return manifest } @@ -627,7 +467,7 @@ export default class Player extends Core { } if (this.track_next_instances.length === 0) { - this.console.log(`[PLAYER] No more tracks to play, stopping...`) + this.console.log(`No more tracks to play, stopping...`) return this.stop() } @@ -683,7 +523,7 @@ export default class Player extends Core { ) setTimeout(() => { - this.track_instance.media.pause() + this.track_instance.audio.pause() resolve() }, Player.gradualFadeMs) @@ -705,7 +545,7 @@ export default class Player extends Core { // ensure audio elemeto starts from 0 volume this.track_instance.gainNode.gain.value = 0.0001 - this.track_instance.media.play().then(() => { + this.track_instance.audio.play().then(() => { resolve() }) @@ -743,7 +583,7 @@ export default class Player extends Core { if (typeof to === "boolean") { this.state.muted = to - this.track_instance.media.muted = to + this.track_instance.audio.muted = to } return this.state.muted @@ -783,13 +623,13 @@ export default class Player extends Core { } seek(time, { sync = false } = {}) { - if (!this.track_instance || !this.track_instance.media) { + if (!this.track_instance || !this.track_instance.audio) { return false } // if time not provided, return current time if (typeof time === "undefined") { - return this.track_instance.media.currentTime + return this.track_instance.audio.currentTime } if (this.state.control_locked && !sync) { @@ -797,9 +637,12 @@ export default class Player extends Core { return false } + // if time is provided, seek to that time if (typeof time === "number") { - this.track_instance.media.currentTime = time + this.console.log(`Seeking to ${time} | Duration: ${this.track_instance.audio.duration}`) + + this.track_instance.audio.currentTime = time return time } @@ -813,7 +656,7 @@ export default class Player extends Core { this.state.playback_mode = mode if (this.track_instance) { - this.track_instance.media.loop = this.state.playback_mode === "repeat" + this.track_instance.audio.loop = this.state.playback_mode === "repeat" } AudioPlayerStorage.set("mode", mode) @@ -826,7 +669,7 @@ export default class Player extends Core { return false } - return this.track_instance.media.duration + return this.track_instance.audio.duration } loop(to) { @@ -837,8 +680,8 @@ export default class Player extends Core { this.state.loop = to ?? !this.state.loop - if (this.track_instance.media) { - this.track_instance.media.loop = this.state.loop + if (this.track_instance.audio) { + this.track_instance.audio.loop = this.state.loop } return this.state.loop @@ -897,45 +740,6 @@ export default class Player extends Core { return this.mute(to) } - async getTracksByIds(list) { - if (!Array.isArray(list)) { - this.console.warn("List must be an array") - return false - } - - let ids = [] - - list.forEach((item) => { - if (typeof item === "string") { - ids.push(item) - } - }) - - if (ids.length === 0) { - return list - } - - const fetchedTracks = await MusicModel.getTracksData(ids).catch((err) => { - this.console.error(err) - return false - }) - - if (!fetchedTracks) { - return list - } - - // replace fetched tracks with the ones in the list - fetchedTracks.forEach((fetchedTrack) => { - const index = list.findIndex((item) => item === fetchedTrack._id) - - if (index !== -1) { - list[index] = fetchedTrack - } - }) - - return list - } - async setSampleRate(to) { // must be a integer if (typeof to !== "number") { @@ -973,38 +777,4 @@ export default class Player extends Core { }) }) } - - async toggleCurrentTrackLike(to, manifest) { - let isCurrent = !!!manifest - - if (typeof manifest === "undefined") { - manifest = this.track_instance.manifest - } - - if (!manifest) { - this.console.error("Track instance or manifest not found") - return false - } - - if (typeof to !== "boolean") { - this.console.warn("Like must be a boolean") - return false - } - - const service = manifest.service ?? "default" - - if (!ServicesHandlers[service].toggleLike) { - this.console.error(`Service [${service}] does not support like actions`) - return false - } - - const result = await ServicesHandlers[service].toggleLike(manifest, to) - - if (isCurrent) { - this.track_instance.manifest.liked = to - this.state.track_manifest.liked = to - } - - return result - } } \ No newline at end of file diff --git a/packages/app/src/cores/player/processors/node.js b/packages/app/src/cores/player/processors/node.js index 1fd83628..84a6a865 100755 --- a/packages/app/src/cores/player/processors/node.js +++ b/packages/app/src/cores/player/processors/node.js @@ -72,7 +72,7 @@ export default class ProcessorNode { prevNode.processor._last.connect(this.processor) } else { //console.log(`[${this.constructor.refName ?? this.constructor.name}] node is the first node, connecting to the media source...`) - instance.track.connect(this.processor) + instance.contextElement.connect(this.processor) } // now, check if it has a next node diff --git a/packages/app/src/cores/player/services.js b/packages/app/src/cores/player/services.js index 363a998c..32508f5e 100755 --- a/packages/app/src/cores/player/services.js +++ b/packages/app/src/cores/player/services.js @@ -1,21 +1,59 @@ import MusicModel from "comty.js/models/music" -export default { - "default": { - resolve: async (track_id) => { - return await MusicModel.getTrackData(track_id) - }, - resolveMany: async (track_ids, options) => { - const response = await MusicModel.getTrackData(track_ids, options) +class ComtyMusicService { + static id = "default" - if (response.list) { - return response - } + resolve = async (track_id) => { + return await MusicModel.getTrackData(track_id) + } - return [response] - }, - toggleLike: async (manifest, to) => { - return await MusicModel.toggleTrackLike(manifest, to) + resolveMany = async (track_ids, options) => { + const response = await MusicModel.getTrackData(track_ids, options) + + if (response.list) { + return response } + + return [response] + } + + toggleTrackLike = async (manifest, to) => { + return await MusicModel.toggleTrackLike(manifest, to) + } +} + +export default class ServiceProviders { + providers = [ + new ComtyMusicService() + ] + + findProvider(providerId) { + return this.providers.find((provider) => provider.constructor.id === providerId) + } + + register(provider) { + this.providers.push(provider) + } + + // operations + resolve = async (providerId, manifest) => { + const provider = await this.findProvider(providerId) + + if (!provider) { + console.error(`Failed to resolve manifest, provider [${providerId}] not registered`) + return manifest + } + + return await provider.resolve(manifest) + } + + resolveMany = async (manifests) => { + manifests = manifests.map(async (manifest) => { + return await this.resolve(manifest.service ?? "default", manifest) + }) + + manifests = await Promise.all(manifests) + + return manifests } } \ No newline at end of file diff --git a/packages/app/src/cores/sfx/sfx.core.js b/packages/app/src/cores/sfx/sfx.core.js index a0577e64..51841fc8 100755 --- a/packages/app/src/cores/sfx/sfx.core.js +++ b/packages/app/src/cores/sfx/sfx.core.js @@ -55,8 +55,6 @@ export default class SFXCore extends Core { src: [path], }) } - - this.console.log(this.soundsPool) } async play(name, options = {}) { diff --git a/packages/app/src/cores/style/style.core.jsx b/packages/app/src/cores/style/style.core.jsx index 12096da6..47b594f6 100755 --- a/packages/app/src/cores/style/style.core.jsx +++ b/packages/app/src/cores/style/style.core.jsx @@ -11,22 +11,32 @@ const variantToAlgorithm = { dark: theme.darkAlgorithm, } +const ClientPrefersDark = () => window.matchMedia("(prefers-color-scheme: dark)") + +function variantKeyToColor(key) { + if (key == "auto") { + if (ClientPrefersDark().matches) { + return "dark" + } + + return "light" + } + + return key +} + + export class ThemeProvider extends React.Component { state = { - useAlgorigthm: app.cores.style.currentVariant ?? "dark", - useCompactMode: app.cores.style.getValue("compact-mode"), + useAlgorigthm: variantKeyToColor(app.cores.style.currentVariantKey), + useCompactMode: app.cores.style.getVar("compact-mode"), } handleUpdate = (update) => { console.log("[THEME] Update", update) - if (update.themeVariant) { - this.setState({ - useAlgorigthm: update.themeVariant - }) - } - this.setState({ + useAlgorigthm: variantKeyToColor(app.cores.style.currentVariantKey), useCompactMode: update["compact-mode"] }) } @@ -51,7 +61,7 @@ export class ThemeProvider extends React.Component { return this.getVar(...args), + getDefaultVar: (...args) => this.getDefaultVar(...args), + getStoragedVariantKey: () => StyleCore.storagedVariantKey, - return StyleCore.storagedVariant + applyStyles: (...args) => this.applyStyles(...args), + applyVariant: (...args) => this.applyVariant(...args), + applyTemporalVariant: (...args) => this.applyTemporalVariant(...args), + + mutateTheme: (...args) => this.mutateTheme(...args), + resetToDefault: () => this.resetToDefault(), + toggleCompactMode: () => this.toggleCompactMode(), } async onInitialize() { - if (StyleCore.storagedTheme) { - // TODO: Start remote theme loader - } else { - this.public.theme = config.defaultTheme - } + this.public.theme = config.defaultTheme const modifications = StyleCore.storagedModifications // override with static vars if (this.public.theme.defaultVars) { - this.update(this.public.theme.defaultVars) + this.applyStyles(this.public.theme.defaultVars) } // override theme with modifications if (modifications) { - this.update(modifications) + this.applyStyles(modifications) } // apply variation - this.applyVariant(StyleCore.variant) - - // handle auto prefered color scheme - window.matchMedia("(prefers-color-scheme: light)").addListener(() => { - this.console.log(`[THEME] Auto color scheme changed`) - - this.applyVariant(StyleCore.variant) - }) + this.applyVariant(StyleCore.storagedVariantKey ?? StyleCore.defaultVariantKey) // if mobile set fontScale to 1 if (app.isMobile) { - this.update({ + this.applyStyles({ fontScale: 1 }) } - } - onEvents = { - "style:auto_darkmode": (value) => { - if (value === true) { - return this.applyVariant(StyleCore.variant) + ClientPrefersDark().addEventListener("change", (event) => { + this.console.log("[PREFERS-DARK] Change >", event.matches) + + if (this.isOnTemporalVariant) { + return false } - return this.applyVariant(StyleCore.variant) - } + if (event.matches) { + this.applyVariant("dark") + } else { + this.applyVariant("light") + } + }) } - public = { - theme: null, - mutation: null, - currentVariant: "dark", - - getValue: (...args) => this.getValue(...args), - setDefault: () => this.setDefault(), - update: (...args) => this.update(...args), - applyVariant: (...args) => this.applyVariant(...args), - applyInitialVariant: () => this.applyVariant(StyleCore.variant), - compactMode: (value = !window.app.cores.settings.get("style.compactMode")) => { - if (value) { - return this.update({ - layoutMargin: 0, - layoutPadding: 0, - }) - } - - return this.update({ - layoutMargin: this.getValue("layoutMargin"), - layoutPadding: this.getValue("layoutPadding"), - }) - }, - modify: (value) => { - this.public.update(value) - - this.applyVariant(this.public.mutation.themeVariant ?? this.public.currentVariant) - - StyleCore.storagedModifications = this.public.mutation - }, - defaultVar: (key) => { - if (!key) { - return this.public.theme.defaultVars - } - - return this.public.theme.defaultVars[key] - }, - storagedVariant: StyleCore.storagedVariant, - storagedModifications: StyleCore.storagedModifications, - } - - getValue(key) { + getVar(key) { if (typeof key === "undefined") { return { ...this.public.theme.defaultVars, - ...StyleCore.storagedModifications + ...StyleCore.storagedModifications, } } return StyleCore.storagedModifications[key] || this.public.theme.defaultVars[key] } - setDefault() { - store.remove(StyleCore.themeManifestStorageKey) - store.remove(StyleCore.modificationStorageKey) + getDefaultVar(key) { + if (!key) { + return this.public.theme.defaultVars + } - app.cores.settings.set("colorPrimary", this.public.theme.defaultVars.colorPrimary) - - this.onInitialize() + return this.public.theme.defaultVars[key] } - update(update) { + applyStyles(update) { if (typeof update !== "object") { + this.console.error("Invalid update, must be an object") return false } @@ -241,18 +214,62 @@ export default class StyleCore extends Core { }) } - applyVariant(variant = (this.public.theme.defaultVariant ?? "light")) { - const values = this.public.theme.variants[variant] + applyVariant = (variantKey = (this.public.theme.defaultVariant ?? "light"), save = true) => { + if (save) { + StyleCore.storagedVariantKey = variantKey + this.public.currentVariantKey = variantKey + } + + this.isOnTemporalVariant = false + + this.console.log(`Input variant key [${variantKey}]`) + + const color = variantKeyToColor(variantKey) + + this.console.log(`Applying variant [${color}]`) + + const values = this.public.theme.variants[color] if (!values) { - this.console.error(`Variant [${variant}] not found`) + this.console.error(`Variant [${color}] not found`) return false } - values.themeVariant = variant + this.applyStyles(values) + } - this.public.currentVariant = variant + applyTemporalVariant = (variantKey) => { + this.applyVariant(variantKey, false) - this.update(values) + this.isOnTemporalVariant = true + } + + mutateTheme(update) { + this.applyStyles(update) + this.applyVariant(this.public.currentVariantKey) + + StyleCore.storagedModifications = this.public.mutation + } + + toggleCompactMode(value = !window.app.cores.settings.get("style.compactMode")) { + if (value === true) { + return this.applyStyles({ + layoutMargin: 0, + layoutPadding: 0, + }) + } + + return this.applyStyles({ + layoutMargin: this.getVar("layoutMargin"), + layoutPadding: this.getVar("layoutPadding"), + }) + } + + resetToDefault() { + store.remove(StyleCore.modificationStorageKey) + + app.cores.settings.set("colorPrimary", this.public.theme.defaultVars.colorPrimary) + + this.onInitialize() } } \ No newline at end of file diff --git a/packages/app/src/hooks/useChat/index.js b/packages/app/src/hooks/useChat/index.js index a19d4139..8cb12cd3 100644 --- a/packages/app/src/hooks/useChat/index.js +++ b/packages/app/src/hooks/useChat/index.js @@ -9,7 +9,7 @@ export default (to_user_id) => { const [isRemoteTyping, setIsRemoteTyping] = React.useState(false) const [timeoutOffTypingEvent, setTimeoutOffTypingEvent] = React.useState(null) - + async function sendMessage(message) { emitTypingEvent(false) diff --git a/packages/app/src/hooks/useClickNavById/index.js b/packages/app/src/hooks/useClickNavById/index.js new file mode 100644 index 00000000..b122a6f6 --- /dev/null +++ b/packages/app/src/hooks/useClickNavById/index.js @@ -0,0 +1,39 @@ +import React from "react" + +const useClickNavById = (navigators = {}, itemFlagId = "div") => { + const ref = React.useRef(null) + + async function onClick(e) { + const element = e.target.closest(itemFlagId ?? "div") + + if (!element) { + console.error("Element not found") + return false + } + + const id = element?.id + + if (!id) { + console.error("Element id not found") + return false + } + + const location = navigators[id] + + if (!location) { + console.error("Location not found") + return false + } + + app.location.push(location) + } + + return [ + ref, + { + onClick + } + ] +} + +export default useClickNavById \ No newline at end of file diff --git a/packages/app/src/hooks/useHideOnMouseStop/index.jsx b/packages/app/src/hooks/useHideOnMouseStop/index.jsx new file mode 100644 index 00000000..ef240468 --- /dev/null +++ b/packages/app/src/hooks/useHideOnMouseStop/index.jsx @@ -0,0 +1,59 @@ +import React from "react" + +let timer = null + +const useHideOnMouseStop = ({ + delay = 2000, + hideCursor = false, + initialHide = false, + showOnlyOnContainerHover = false, +}) => { + const [hide, setHide] = React.useState(initialHide) + const mountedRef = React.useRef(false) + const [hover, setHover] = React.useState(false) + const toggleVisibility = React.useCallback((hide, cursor) => { + setHide(hide) + if (hideCursor) { + document.body.style.cursor = cursor + } + }, [hideCursor]) + const onMouseEnter = React.useCallback(() => setHover(true), [setHover]) + const onMouseLeave = React.useCallback(() => setHover(false), [setHover]) + const onMouseMove = React.useCallback(() => { + clearTimeout(timer) + + if (hide && mountedRef.current) { + if (showOnlyOnContainerHover && hover) { + toggleVisibility(!hide, "default") + } else if (!showOnlyOnContainerHover) { + toggleVisibility(!hide, "default") + } + } + + timer = setTimeout(() => { + if (!hover && mountedRef.current) { + toggleVisibility(true, "none") + } + }, delay) + }, [hide, hover, setHide]) + + React.useEffect(() => { + mountedRef.current = true + + return () => { + mountedRef.current = false + } + }, []) + + React.useEffect(() => { + window.addEventListener("mousemove", onMouseMove) + + return () => { + window.removeEventListener("mousemove", onMouseMove) + } + }, [onMouseMove]) + + return [hide, onMouseEnter, onMouseLeave] +} + +export default useHideOnMouseStop \ No newline at end of file diff --git a/packages/app/src/utils/useMaxScreen/index.js b/packages/app/src/hooks/useMaxScreen/index.js similarity index 60% rename from packages/app/src/utils/useMaxScreen/index.js rename to packages/app/src/hooks/useMaxScreen/index.js index 0ff14951..5c2dae74 100644 --- a/packages/app/src/utils/useMaxScreen/index.js +++ b/packages/app/src/hooks/useMaxScreen/index.js @@ -2,15 +2,16 @@ import React from "react" export default () => { const enterPlayerAnimation = () => { - app.cores.style.applyVariant("dark") - app.cores.style.compactMode(true) + app.cores.style.applyTemporalVariant("dark") + app.cores.style.toggleCompactMode(true) app.layout.toggleCenteredContent(false) app.controls.toggleUIVisibility(false) } const exitPlayerAnimation = () => { - app.cores.style.applyInitialVariant() - app.cores.style.compactMode(false) + app.cores.style.applyVariant(app.cores.style.getStoragedVariantKey()) + app.cores.style.toggleCompactMode(false) + app.layout.toggleCenteredContent(true) app.controls.toggleUIVisibility(true) } diff --git a/packages/app/src/layouts/components/bottomBar/index.jsx b/packages/app/src/layouts/components/bottomBar/index.jsx index eff618e9..8108dad1 100755 --- a/packages/app/src/layouts/components/bottomBar/index.jsx +++ b/packages/app/src/layouts/components/bottomBar/index.jsx @@ -31,10 +31,10 @@ const tourSteps = [ ] const openPlayerView = () => { - app.DrawerController.open("player", PlayerView) + app.layout.drawer.open("player", PlayerView) } const openCreator = () => { - app.DrawerController.open("creator", CreatorView, { + app.layout.drawer.open("creator", CreatorView, { props: { bodyStyle: { minHeight: "unset", @@ -336,7 +336,7 @@ export class BottomBar extends React.Component {
} - const heightValue = this.state.visible ? Number(app.cores.style.defaultVar("bottom-bar-height").replace("px", "")) : 0 + const heightValue = this.state.visible ? Number(app.cores.style.getDefaultVar("bottom-bar-height").replace("px", "")) : 0 return <> { diff --git a/packages/app/src/layouts/components/drawer/index.jsx b/packages/app/src/layouts/components/drawer/index.jsx index b3747770..c351dbbb 100755 --- a/packages/app/src/layouts/components/drawer/index.jsx +++ b/packages/app/src/layouts/components/drawer/index.jsx @@ -13,7 +13,7 @@ export default class DrawerController extends React.Component { drawers: [], } - window.app["DrawerController"] = { + app.layout.drawer = { open: this.open, close: this.close, closeAll: this.closeAll, diff --git a/packages/app/src/layouts/components/sidebar/index.jsx b/packages/app/src/layouts/components/sidebar/index.jsx index 523dffb1..0bcdc791 100755 --- a/packages/app/src/layouts/components/sidebar/index.jsx +++ b/packages/app/src/layouts/components/sidebar/index.jsx @@ -11,17 +11,13 @@ import sidebarItems from "@config/sidebar" import "./index.less" -const extraItems = [ - { - id: "insiders", - title: "Insiders", - icon: "MdToken", - roles: ["insider"], - path: "/insiders", - } -] - const onClickHandlers = { + addons: () => { + window.app.location.push("/addons") + }, + studio: () => { + window.app.location.push("/studio") + }, settings: () => { window.app.navigation.goToSettings() }, @@ -99,11 +95,29 @@ const BottomMenuDefaultItems = [ const ActionMenuItems = [ { - key: "account", + key: "profile", label: <> - {t => t("Account")} + {t => t("Profile")} + + , + }, + { + key: "studio", + label: <> + + + {t => t("Studio")} + + , + }, + { + key: "addons", + label: <> + + + {t => t("Addons")} , }, @@ -256,8 +270,6 @@ export default class Sidebar extends React.Component { } componentDidMount = async () => { - this.computeExtraItems() - for (const [event, handler] of Object.entries(this.events)) { app.eventBus.on(event, handler) } @@ -279,28 +291,6 @@ export default class Sidebar extends React.Component { //delete app.layout.sidebar } - computeExtraItems = async () => { - const roles = await app.cores.permissions.getRoles() - - const resultItems = [] - - if (roles.includes("admin")) { - resultItems.push(...extraItems) - } else { - extraItems.forEach((item) => { - item.roles.every((role) => { - if (roles.includes(role)) { - resultItems.push(item) - } - }) - }) - } - - this.setState({ - topItems: generateTopItems(resultItems) - }) - } - handleClick = (e) => { if (e.item.props.ignore_click === "true") { return @@ -470,7 +460,6 @@ export default class Sidebar extends React.Component { mode="inline" onClick={this.handleClick} items={this.getBottomItems()} - /> diff --git a/packages/app/src/layouts/components/toolsBar/index.jsx b/packages/app/src/layouts/components/toolsBar/index.jsx index f222180e..ee9c8270 100755 --- a/packages/app/src/layouts/components/toolsBar/index.jsx +++ b/packages/app/src/layouts/components/toolsBar/index.jsx @@ -55,26 +55,28 @@ export default class ToolsBar extends React.Component { } render() { - return + return {({ x, width }) => { return
{/*
diff --git a/packages/app/src/layouts/components/toolsBar/index.less b/packages/app/src/layouts/components/toolsBar/index.less index a34b8e07..e94a0f1d 100755 --- a/packages/app/src/layouts/components/toolsBar/index.less +++ b/packages/app/src/layouts/components/toolsBar/index.less @@ -6,15 +6,22 @@ top: 0; right: 0; - max-width: 420px; - min-width: 320px; - height: 100vh; height: 100dvh; - - padding: 10px; + max-width: 420px; z-index: 150; + padding: 10px; + + .visible { + min-width: 320px; + } + + &:not(.visible) { + min-width: 0; + padding: 0; + + } } .tools-bar { @@ -29,13 +36,13 @@ border-radius: @sidebar_borderRadius; box-shadow: @card-shadow; + padding: 10px; + background-color: var(--background-color-accent); gap: 20px; - &.visible { - padding: 10px; - } + flex: 0; .card { display: flex; diff --git a/packages/app/src/layouts/components/topBar/index.jsx b/packages/app/src/layouts/components/topBar/index.jsx index 3bacac30..0700e132 100755 --- a/packages/app/src/layouts/components/topBar/index.jsx +++ b/packages/app/src/layouts/components/topBar/index.jsx @@ -85,7 +85,7 @@ export default (props) => { } }, [render]) - const heightValue = visible ? Number(app.cores.style.defaultVar("top-bar-height").replace("px", "")) : 0 + const heightValue = visible ? Number(app.cores.style.getDefaultVar("top-bar-height").replace("px", "")) : 0 return { - return
-
+ return
+
-
+

{props.title}

@@ -26,16 +26,16 @@ const FieldItem = (props) => {
} -const ExtensionsBrowser = () => { - return
-
+const AddonsBrowser = () => { + return
+

- Extensions + Addons

-
+
{
} -const Marketplace = () => { - return
-
-
+const addons = () => { + return
+
+

- Marketplace + Addons

- - - + + +
} -export default Marketplace \ No newline at end of file +export default addons \ No newline at end of file diff --git a/packages/app/src/pages/marketplace/index.less b/packages/app/src/pages/addons/index.less similarity index 86% rename from packages/app/src/pages/marketplace/index.less rename to packages/app/src/pages/addons/index.less index c9da0256..5dfb8ab1 100644 --- a/packages/app/src/pages/marketplace/index.less +++ b/packages/app/src/pages/addons/index.less @@ -1,4 +1,4 @@ -.marketplace { +.addons-page { display: flex; flex-direction: column; @@ -6,7 +6,7 @@ width: 100%; - .marketplace-header { + .addons-header { display: flex; flex-direction: row; @@ -16,7 +16,7 @@ width: 100%; - .marketplace-header-card { + .addons-header-card { display: flex; flex-direction: row; @@ -37,20 +37,20 @@ } } - .marketplace-field { + .addons-field { display: flex; flex-direction: column; - .marketplace-field-header {} + .addons-field-header {} - .marketplace-field-slider { + .addons-field-slider { display: flex; flex-direction: row; gap: 20px; } - .marketplace-field-item { + .addons-field-item { position: relative; display: flex; @@ -67,7 +67,7 @@ padding: 10px; - .marketplace-field-item-image { + .addons-field-item-image { width: 100%; height: 60%; @@ -90,7 +90,7 @@ } } - .marketplace-field-item-info { + .addons-field-item-info { display: flex; flex-direction: column; diff --git a/packages/app/src/pages/music/creator/components/BasicInformation/index.jsx b/packages/app/src/pages/creator/music/components/BasicInformation/index.jsx similarity index 100% rename from packages/app/src/pages/music/creator/components/BasicInformation/index.jsx rename to packages/app/src/pages/creator/music/components/BasicInformation/index.jsx diff --git a/packages/app/src/pages/music/creator/components/TracksUploads/index.jsx b/packages/app/src/pages/creator/music/components/TracksUploads/index.jsx similarity index 99% rename from packages/app/src/pages/music/creator/components/TracksUploads/index.jsx rename to packages/app/src/pages/creator/music/components/TracksUploads/index.jsx index 514ef390..13e28ed9 100755 --- a/packages/app/src/pages/music/creator/components/TracksUploads/index.jsx +++ b/packages/app/src/pages/creator/music/components/TracksUploads/index.jsx @@ -332,7 +332,7 @@ const FileListItem = (props) => { export default (props) => { const onClickEditTrack = (track) => { - app.DrawerController.open("track_editor", FileItemEditor, { + app.layout.drawer.open("track_editor", FileItemEditor, { type: "drawer", props: { width: "30vw", diff --git a/packages/app/src/pages/music/creator/components/TracksUploads/index.less b/packages/app/src/pages/creator/music/components/TracksUploads/index.less similarity index 100% rename from packages/app/src/pages/music/creator/components/TracksUploads/index.less rename to packages/app/src/pages/creator/music/components/TracksUploads/index.less diff --git a/packages/app/src/pages/music/creator/index.jsx b/packages/app/src/pages/creator/music/index.jsx similarity index 100% rename from packages/app/src/pages/music/creator/index.jsx rename to packages/app/src/pages/creator/music/index.jsx diff --git a/packages/app/src/pages/music/creator/index.less b/packages/app/src/pages/creator/music/index.less similarity index 100% rename from packages/app/src/pages/music/creator/index.less rename to packages/app/src/pages/creator/music/index.less diff --git a/packages/app/src/pages/lyrics/components/controller/index.jsx b/packages/app/src/pages/lyrics/components/controller/index.jsx index 564cf8fe..85e21477 100644 --- a/packages/app/src/pages/lyrics/components/controller/index.jsx +++ b/packages/app/src/pages/lyrics/components/controller/index.jsx @@ -1,8 +1,10 @@ import React from "react" -import { Tag } from "antd" +import { Tag, Button } from "antd" import classnames from "classnames" import Marquee from "react-fast-marquee" +import useHideOnMouseStop from "@hooks/useHideOnMouseStop" + import { Icons } from "@components/Icons" import Controls from "@components/Player/Controls" @@ -49,6 +51,7 @@ const PlayerController = React.forwardRef((props, ref) => { const titleRef = React.useRef() + const [hide, onMouseEnter, onMouseLeave] = useHideOnMouseStop({ delay: 3000, hideCursor: true }) const [titleIsOverflown, setTitleIsOverflown] = React.useState(false) const [currentTime, setCurrentTime] = React.useState(0) @@ -61,6 +64,7 @@ const PlayerController = React.forwardRef((props, ref) => { setDraggingTime(false) app.cores.player.seek(seekTime) + syncPlayback() } async function syncPlayback() { @@ -87,12 +91,24 @@ const PlayerController = React.forwardRef((props, ref) => { React.useEffect(() => { setTitleIsOverflown(isOverflown(titleRef.current)) setTrackDuration(app.cores.player.duration()) + console.log(context.track_manifest) }, [context.track_manifest]) + React.useEffect(() => { + syncPlayback() + }, []) + const isStopped = context.playback_status === "stopped" return
@@ -174,7 +190,6 @@ const PlayerController = React.forwardRef((props, ref) => {
{ context.track_manifest?.metadata.lossless && } bordered={false} > @@ -188,6 +203,22 @@ const PlayerController = React.forwardRef((props, ref) => { Explicit } + { + props.lyrics?.sync_audio_at && } + > + Video + + } + { + props.lyrics?.available_langs &&
diff --git a/packages/app/src/pages/lyrics/components/text/index.jsx b/packages/app/src/pages/lyrics/components/text/index.jsx index f4b0485e..e6b2f089 100644 --- a/packages/app/src/pages/lyrics/components/text/index.jsx +++ b/packages/app/src/pages/lyrics/components/text/index.jsx @@ -54,6 +54,8 @@ const LyricsText = React.forwardRef((props, textRef) => { if (currentLineIndex === 0) { setVisible(false) } else { + setVisible(true) + console.log(`Scrolling to line ${currentLineIndex}`) // find line element by id const lineElement = textRef.current.querySelector(`#lyrics-line-${currentLineIndex}`) @@ -63,24 +65,26 @@ const LyricsText = React.forwardRef((props, textRef) => { behavior: "smooth", block: "center", }) + } else { + // scroll to top + textRef.current.scrollTop = 0 } } }, [currentLineIndex]) //* Handle when playback status change React.useEffect(() => { - if (lyrics) { - if (typeof lyrics?.lrc !== "undefined") { - if (context.playback_status === "playing") { - startSyncInterval() - } else { - if (syncInterval) { - clearInterval(syncInterval) - } - } startSyncInterval() + if (typeof lyrics?.lrc !== "undefined") { + if (context.playback_status === "playing") { + startSyncInterval() + } else { + if (syncInterval) { + clearInterval(syncInterval) + } } + } else { + clearInterval(syncInterval) } - }, [context.playback_status]) //* Handle when lyrics object change @@ -96,6 +100,12 @@ const LyricsText = React.forwardRef((props, textRef) => { } }, [lyrics]) + React.useEffect(() => { + setVisible(false) + clearInterval(syncInterval) + setCurrentLineIndex(0) + }, [context.track_manifest]) + React.useEffect(() => { return () => { clearInterval(syncInterval) diff --git a/packages/app/src/pages/lyrics/components/video/index.jsx b/packages/app/src/pages/lyrics/components/video/index.jsx index d8df998f..ab985f81 100644 --- a/packages/app/src/pages/lyrics/components/video/index.jsx +++ b/packages/app/src/pages/lyrics/components/video/index.jsx @@ -1,5 +1,7 @@ import React from "react" +import classnames from "classnames" +import useHideOnMouseStop from "@hooks/useHideOnMouseStop" import { Context } from "@contexts/WithPlayerContext" const maxLatencyInMs = 55 @@ -32,6 +34,13 @@ const LyricsVideo = React.forwardRef((props, videoRef) => { // if `sync_audio_at_ms` is present, it means the video must be synced with audio if (lyrics.video_source && typeof lyrics.sync_audio_at_ms !== "undefined") { + if (!videoRef.current) { + clearInterval(syncInterval) + setSyncInterval(null) + setCurrentVideoLatency(0) + return false + } + const currentTrackTime = app.cores.player.seek() const currentVideoTime = videoRef.current.currentTime - (lyrics.sync_audio_at_ms / 1000) @@ -55,7 +64,7 @@ const LyricsVideo = React.forwardRef((props, videoRef) => { } function startSyncInterval() { - setSyncInterval(setInterval(syncPlayback, 100)) + setSyncInterval(setInterval(syncPlayback, 300)) } React.useEffect(() => { @@ -97,6 +106,17 @@ const LyricsVideo = React.forwardRef((props, videoRef) => { } }, [context.playback_status]) + React.useEffect(() => { + if (context.loading === true && context.playback_status === "playing") { + videoRef.current.pause() + } + + if (context.loading === false && context.playback_status === "playing") { + videoRef.current.play() + } + + }, [context.loading]) + //* Handle when lyrics object change React.useEffect(() => { if (lyrics) { @@ -141,17 +161,24 @@ const LyricsVideo = React.forwardRef((props, videoRef) => { }, []) return <> -
-
-

Maximun latency

-

{maxLatencyInMs}ms

+ { + props.lyrics?.sync_audio_at &&
+
+

Maximun latency

+

{maxLatencyInMs}ms

+
+
+

Video Latency

+

{(currentVideoLatency * 1000).toFixed(2)}ms

+
+ {syncingVideo ?

Syncing video...

: null}
-
-

Video Latency

-

{(currentVideoLatency * 1000).toFixed(2)}ms

-
- {syncingVideo ?

Syncing video...

: null} -
+ }
} -const ReleasesList = (props) => { - const hopNumber = props.hopsPerPage ?? 6 - - const [offset, setOffset] = React.useState(0) - const [ended, setEnded] = React.useState(false) - - const [loading, result, error, makeRequest] = app.cores.api.useRequest(props.fetchMethod, { - limit: hopNumber, - trim: offset - }) - - const onClickPrev = () => { - if (offset === 0) { - return - } - - setOffset((value) => { - const newOffset = value - hopNumber - - // check if newOffset is NaN - if (newOffset !== newOffset) { - return false - } - - if (typeof makeRequest === "function") { - makeRequest({ - trim: newOffset, - limit: hopNumber, - }) - } - - return newOffset - }) - } - - const onClickNext = () => { - if (ended) { - return - } - - setOffset((value) => { - const newOffset = value + hopNumber - - // check if newOffset is NaN - if (newOffset !== newOffset) { - return false - } - - if (typeof makeRequest === "function") { - makeRequest({ - trim: newOffset, - limit: hopNumber, - }) - } - - return newOffset - }) - } - - React.useEffect(() => { - if (result) { - setEnded(result.length < hopNumber) - } - }, [result]) - - if (error) { - console.error(error) - - return
- -
- } - - return
-
-

- { - props.headerIcon - } - - {(t) => t(props.headerTitle)} - -

- -
- } - onClick={onClickPrev} - disabled={offset === 0 || loading} - /> - - } - onClick={onClickNext} - disabled={ended || loading} - /> -
-
-
- { - loading && - } - { - !loading && result.items.map((playlist, index) => { - return - }) - } -
-
-} - const ResultGroupsDecorators = { "playlists": { icon: "MdPlaylistPlay", diff --git a/packages/app/src/pages/music/components/explore/index.less b/packages/app/src/pages/music/tabs/explore/index.less similarity index 83% rename from packages/app/src/pages/music/components/explore/index.less rename to packages/app/src/pages/music/tabs/explore/index.less index 086cb8d0..ba35a49c 100755 --- a/packages/app/src/pages/music/components/explore/index.less +++ b/packages/app/src/pages/music/tabs/explore/index.less @@ -150,59 +150,6 @@ html { overflow-x: visible; } - - .playlistExplorer_section { - display: flex; - flex-direction: column; - - overflow-x: visible; - - .playlistExplorer_section_header { - display: flex; - flex-direction: row; - - align-items: center; - - margin-bottom: 20px; - - h1 { - font-size: 1.5rem; - margin: 0; - } - - .playlistExplorer_section_header_actions { - display: flex; - flex-direction: row; - - gap: 10px; - - align-self: center; - - margin-left: auto; - } - } - - .playlistExplorer_section_list { - display: grid; - - grid-gap: 20px; - grid-template-columns: repeat(3, minmax(0, 1fr)); - - min-width: 372px !important; - - @media (min-width: 2000px) { - grid-template-columns: repeat(4, 1fr); - } - - @media (min-width: 2300px) { - grid-template-columns: repeat(5, 1fr); - } - - .playlistItem { - justify-self: center; - } - } - } } .music-explorer_search_results { diff --git a/packages/app/src/pages/music/components/favorites/index.jsx b/packages/app/src/pages/music/tabs/favorites/index.jsx similarity index 100% rename from packages/app/src/pages/music/components/favorites/index.jsx rename to packages/app/src/pages/music/tabs/favorites/index.jsx diff --git a/packages/app/src/pages/music/tabs.jsx b/packages/app/src/pages/music/tabs/index.jsx similarity index 77% rename from packages/app/src/pages/music/tabs.jsx rename to packages/app/src/pages/music/tabs/index.jsx index fc1d3e38..6611ecc2 100755 --- a/packages/app/src/pages/music/tabs.jsx +++ b/packages/app/src/pages/music/tabs/index.jsx @@ -1,6 +1,6 @@ -import LibraryTab from "./components/library" -import FavoritesTab from "./components/favorites" -import ExploreTab from "./components/explore" +import LibraryTab from "./library" +import FavoritesTab from "./favorites" +import ExploreTab from "./explore" export default [ { diff --git a/packages/app/src/pages/music/components/library/index.jsx b/packages/app/src/pages/music/tabs/library/index.jsx similarity index 100% rename from packages/app/src/pages/music/components/library/index.jsx rename to packages/app/src/pages/music/tabs/library/index.jsx diff --git a/packages/app/src/pages/music/components/library/index.less b/packages/app/src/pages/music/tabs/library/index.less similarity index 100% rename from packages/app/src/pages/music/components/library/index.less rename to packages/app/src/pages/music/tabs/library/index.less diff --git a/packages/app/src/pages/music/components/spaces/index.jsx b/packages/app/src/pages/music/tabs/spaces/index.jsx similarity index 100% rename from packages/app/src/pages/music/components/spaces/index.jsx rename to packages/app/src/pages/music/tabs/spaces/index.jsx diff --git a/packages/app/src/pages/music/components/spaces/index.less b/packages/app/src/pages/music/tabs/spaces/index.less similarity index 100% rename from packages/app/src/pages/music/components/spaces/index.less rename to packages/app/src/pages/music/tabs/spaces/index.less diff --git a/packages/app/src/pages/settings/components/SettingItemComponent/index.jsx b/packages/app/src/pages/settings/components/SettingItemComponent/index.jsx index 41f755b9..ab2c3e05 100755 --- a/packages/app/src/pages/settings/components/SettingItemComponent/index.jsx +++ b/packages/app/src/pages/settings/components/SettingItemComponent/index.jsx @@ -1,7 +1,6 @@ import React from "react" import * as antd from "antd" import classnames from "classnames" - import { Translation } from "react-i18next" import { SliderPicker } from "react-color" @@ -9,6 +8,19 @@ import { Icons, createIconRender } from "@components/Icons" import PerformanceLog from "@classes/PerformanceLog" +import "./index.less" + +function shouldUseHorizontalLayout(type) { + switch (type) { + case "switch": + return true + case "button": + return true + default: + return false + } +} + export const SettingsComponents = { button: { component: antd.Button, @@ -400,8 +412,10 @@ export default class SettingItemComponent extends React.PureComponent { className={classnames( "setting_item", { - ["usePadding"]: this.props.setting.usePadding ?? true - })} + ["usePadding"]: this.props.setting.usePadding ?? true, + ["useHorizontal"]: this.props.setting.layout ?? shouldUseHorizontalLayout(String(this.props.setting.component).toLowerCase()) + }) + } >
@@ -414,7 +428,9 @@ export default class SettingItemComponent extends React.PureComponent { {(t) => t(this.props.setting.title ?? this.props.setting.id)} - {this.props.setting.experimental && Experimental } + { + this.props.setting.experimental && Experimental + }

diff --git a/packages/app/src/pages/settings/components/SettingItemComponent/index.less b/packages/app/src/pages/settings/components/SettingItemComponent/index.less new file mode 100644 index 00000000..198ba880 --- /dev/null +++ b/packages/app/src/pages/settings/components/SettingItemComponent/index.less @@ -0,0 +1,121 @@ +.setting_item { + display: flex; + flex-direction: column; + + width: 100%; + + padding: 0 15px; + + &.useHorizontal { + flex-direction: row; + + justify-content: space-between; + + gap: 50px; + + .setting_item_content { + width: fit-content; + } + } + + .uploadButton { + background-color: var(--background-color-primary); + border: 1px solid var(--border-color); + } + + .setting_item_header { + display: inline-flex; + flex-direction: row; + + align-items: center; + justify-content: space-between; + + width: 100%; + + .setting_item_info { + display: flex; + flex-direction: column; + + gap: 7px; + + width: 100%; + + .setting_item_header_title { + display: flex; + flex-direction: row; + + align-items: center; + justify-content: space-between; + + width: 100%; + + color: var(--background-color-contrast); + + h1 { + font-size: 1rem; + margin: 0; + color: var(--background-color-contrast); + } + } + + .setting_item_header_description { + p { + color: var(--background-color-contrast); + font-size: 0.7rem; + margin: 0; + } + } + + } + + .setting_item_header_actions { + display: inline-flex; + align-items: center; + + gap: 10px; + + .ant-btn { + background-color: var(--background-color-primary-2); + } + + .ant-btn:not([disabled]) { + &:hover { + color: var(--colorPrimary); + border: 1px solid var(--colorPrimary); + } + } + } + } + + .setting_item_content { + display: flex; + flex-direction: column; + + --ignore-dragger: true; + padding: 6px 20px; + + width: 100%; + + h1, + h2, + h3, + h4, + h5, + h6, + h3, + p, + span { + color: var(--background-color-contrast); + } + + button { + width: min-content; + } + + .ant-btn.ant-btn-icon-only { + width: 32px; + } + + .ant-select {} + } +} \ No newline at end of file diff --git a/packages/app/src/pages/settings/index.jsx b/packages/app/src/pages/settings/index.jsx index 9c833a94..69be9cff 100755 --- a/packages/app/src/pages/settings/index.jsx +++ b/packages/app/src/pages/settings/index.jsx @@ -122,6 +122,14 @@ export default () => { }) } + React.useEffect(() => { + app.layout.tools_bar.toggleVisibility(false) + + return () => { + app.layout.tools_bar.toggleVisibility(true) + } + }, []) + return

{ + const [navigatorRef, navigatorProps] = useClickNavById(SelectorPaths, ".studio-page-selectors-item") + + return
+
+

Studio

+
+ +
+
+ + Music +
+ +
+ + TV +
+ +
+ + Marketplace +
+
+
+} + +export default StudioPage \ No newline at end of file diff --git a/packages/app/src/pages/studio/index.less b/packages/app/src/pages/studio/index.less new file mode 100644 index 00000000..70ca6eb5 --- /dev/null +++ b/packages/app/src/pages/studio/index.less @@ -0,0 +1,78 @@ +@studio-page-selectors-item-size: 100px; + +.studio-page { + display: flex; + flex-direction: column; + + gap: 20px; + + .studio-page-header { + display: flex; + flex-direction: row; + + align-items: center; + justify-content: center; + + padding: 10px 20px; + gap: 10px; + + background-color: var(--background-color-accent); + border-radius: 12px; + + font-family: "Space Grotesk", sans-serif; + font-size: 1.5rem; + margin: 0; + + h1 { + margin: 0; + } + } + + .studio-page-selectors { + display: flex; + flex-direction: row; + + gap: 10px; + + width: 100px; + height: 100px; + + .studio-page-selectors-item { + display: flex; + flex-direction: column; + + align-items: center; + justify-content: center; + + width: 100%; + height: 100%; + max-width: @studio-page-selectors-item-size; + max-height: @studio-page-selectors-item-size; + min-height: @studio-page-selectors-item-size; + min-width: @studio-page-selectors-item-size; + + padding: 20px; + gap: 10px; + + border: 2px var(--border-color) solid; + border-radius: 12px; + + cursor: pointer; + + transition: all 150ms ease-in-out; + + &:hover { + background-color: var(--background-color-accent); + } + + svg, + span { + margin: 0; + } + + svg { + font-size: 1.3rem; + } + } + } +} \ No newline at end of file diff --git a/packages/app/src/pages/studio/music/[release_id]/index.jsx b/packages/app/src/pages/studio/music/[release_id]/index.jsx new file mode 100644 index 00000000..d51afc29 --- /dev/null +++ b/packages/app/src/pages/studio/music/[release_id]/index.jsx @@ -0,0 +1,13 @@ +import React from "react" + +import ReleaseEditor from "@components/MusicStudio/ReleaseEditor" + +const ReleaseEditorPage = (props) => { + const { release_id } = props.params + + return +} + +export default ReleaseEditorPage \ No newline at end of file diff --git a/packages/app/src/pages/studio/music/index.jsx b/packages/app/src/pages/studio/music/index.jsx new file mode 100644 index 00000000..1122b472 --- /dev/null +++ b/packages/app/src/pages/studio/music/index.jsx @@ -0,0 +1,32 @@ +import React from "react" +import * as antd from "antd" + +import { Icons } from "@components/Icons" + +import MyReleasesList from "@components/MusicStudio/MyReleasesList" + +import "./index.less" + +const MusicStudioPage = (props) => { + return
+
+

Music Studio

+ + } + onClick={() => { + app.location.push("/studio/music/new") + }} + > + New Release + +
+ + +
+} + +export default MusicStudioPage \ No newline at end of file diff --git a/packages/app/src/pages/studio/music/index.less b/packages/app/src/pages/studio/music/index.less new file mode 100644 index 00000000..06f6489e --- /dev/null +++ b/packages/app/src/pages/studio/music/index.less @@ -0,0 +1,25 @@ +.music-studio-page { + display: flex; + flex-direction: column; + + width: 100%; + + gap: 20px; + + .music-studio-page-header { + display: flex; + flex-direction: row; + + align-items: center; + justify-content: space-between; + + width: 100%; + } + + .music-studio-page-content { + display: flex; + flex-direction: column; + + width: 100%; + } +} \ No newline at end of file diff --git a/packages/app/src/pages/tv/live/[id].jsx b/packages/app/src/pages/tv/live/[id].jsx index fcacdb7a..257951ef 100755 --- a/packages/app/src/pages/tv/live/[id].jsx +++ b/packages/app/src/pages/tv/live/[id].jsx @@ -256,15 +256,16 @@ export default class StreamViewer extends React.Component { } enterPlayerAnimation = () => { - app.cores.style.applyVariant("dark") - app.cores.style.compactMode(true) + app.cores.style.applyTemporalVariant("dark") + app.cores.style.toggleCompactMode(true) app.layout.toggleCenteredContent(false) app.controls.toggleUIVisibility(false) } exitPlayerAnimation = () => { - app.cores.style.applyInitialVariant() - app.cores.style.compactMode(false) + app.cores.style.applyVariant(app.cores.style.currentVariantKey) + app.cores.style.toggleCompactMode(false) + app.layout.toggleCenteredContent(true) app.controls.toggleUIVisibility(true) } diff --git a/packages/app/src/settings/accessibility/index.jsx b/packages/app/src/settings/accessibility/index.jsx new file mode 100644 index 00000000..83f17be6 --- /dev/null +++ b/packages/app/src/settings/accessibility/index.jsx @@ -0,0 +1,40 @@ +export default { + id: "accessibility", + icon: "MdAccessibilityNew", + label: "Accessibility", + group: "app", + order: 4, + settings: [ + { + id: "haptics:enabled", + storaged: true, + group: "Accessibility", + component: "Switch", + icon: "MdVibration", + title: "Haptic Feedback", + description: "Enable haptic feedback on touch events.", + desktop: false + }, + { + id: "longPressDelay", + storaged: true, + group: "Accessibility", + component: "Slider", + icon: "MdTimer", + title: "Long press delay", + description: "Set the delay before long press trigger is activated.", + props: { + min: 300, + max: 2000, + step: 100, + marks: { + 300: "0.3s", + 600: "0.6s", + 1000: "1s", + 1500: "1.5s", + 2000: "2s", + } + } + }, + ] +} \ No newline at end of file diff --git a/packages/app/src/settings/apparence/index.jsx b/packages/app/src/settings/apparence/index.jsx index ae09a5ed..8dfdb5f9 100755 --- a/packages/app/src/settings/apparence/index.jsx +++ b/packages/app/src/settings/apparence/index.jsx @@ -12,76 +12,14 @@ export default { order: 1, settings: [ { - id: "style.reduceAnimations", - group: "animations", - component: "Switch", - icon: "MdOutlineSlowMotionVideo", - title: "Reduce animations", - experimental: true, - disabled: true, - storaged: true, - }, - { - id: "style.pageTransitionDuration", - group: "animations", - component: "Slider", - icon: "MdOutlineSpeed", - title: "Page transition duration", - description: "Change the duration of the page transition animation.", - props: { - min: 0, - max: 1000, - step: 50, - marks: { - [app.cores.style.defaultVar("page-transition-duration").replace("ms", "")]: " ", - }, - tooltip: { - formatter: (value) => `${value / 1000}s` - } - }, - defaultValue: () => { - const value = app.cores.style.getValue("page-transition-duration") - - return value ? Number(value.replace("ms", "")) : 250 - }, - onUpdate: (value) => { - app.cores.style.modify({ - "page-transition-duration": `${value}ms` - }) - }, - storaged: true, - }, - { - id: "style:auto_darkmode", + id: "style:variant_mode", group: "aspect", component: "Switch", icon: "Moon", - title: "Sync with system", - description: "Automatically switch to dark mode based on your system preference.", - emitEvent: "style:auto_darkmode", - storaged: true, - }, - { - id: "style:darkmode", - group: "aspect", - component: "Switch", - icon: "Moon", - title: "Dark mode", - description: "Change the theme variant of the application to dark.", - dependsOn: { - "style:auto_darkmode": false - }, - defaultValue: () => { - return app.cores.style.currentVariant === "dark" - }, - onUpdate: (value) => { - app.cores.style.modify({ - themeVariant: value ? "dark" : "light" - }) - - return value - }, - storaged: true + title: "Theme", + description: "Change the theme of the application.", + component: loadable(() => import("../components/themeVariantSelector")), + layout: "horizontal", }, { id: "style.compactMode", @@ -91,10 +29,10 @@ export default { title: "Compact mode", description: "Reduce the size of the application elements.", defaultValue: () => { - return app.cores.style.getValue("compact-mode") + return app.cores.style.getVar("compact-mode") }, onUpdate: (value) => { - app.cores.style.modify({ + app.cores.style.mutateTheme({ "compact-mode": value }) @@ -118,10 +56,10 @@ export default { } }, defaultValue: () => { - return app.cores.style.getValue("fontScale") + return app.cores.style.getVar("fontScale") }, onUpdate: (value) => { - app.cores.style.modify({ + app.cores.style.mutateTheme({ "fontScale": value }) @@ -152,10 +90,10 @@ export default { ] }, defaultValue: () => { - return app.cores.style.getValue("fontFamily") + return app.cores.style.getVar("fontFamily") }, onUpdate: (value) => { - app.cores.style.modify({ + app.cores.style.mutateTheme({ "fontFamily": value }) @@ -169,11 +107,12 @@ export default { component: "SliderColorPicker", title: "Primary color", description: "Change primary color of the application.", + icon: "IoMdColorFill", defaultValue: () => { - return app.cores.style.getValue("colorPrimary") + return app.cores.style.getVar("colorPrimary") }, onUpdate: (value) => { - app.cores.style.modify({ + app.cores.style.mutateTheme({ "colorPrimary": value }) }, @@ -202,261 +141,42 @@ export default { UploadButton ], defaultValue: () => { - const value = app.cores.style.getValue("backgroundImage") + const value = app.cores.style.getVar("backgroundImage") console.log(value) return value ? value.replace(/url\(|\)/g, "") : "" }, onUpdate: (value) => { - app.cores.style.modify({ + app.cores.style.mutateTheme({ backgroundImage: `url(${value})` }) }, storaged: false, }, { - id: "style.backgroundBlur", + id: "style.backgroundTweaker", group: "aspect", - component: "Slider", - icon: "MdBlurOn", - title: "Background blur", - description: "Create a blur effect on the background.", - props: { - min: 0, - max: 50, - step: 1 - }, - defaultValue: () => { - const value = app.cores.style.getValue("backgroundBlur") - - return value ? parseInt(value.replace("px", "")) : 0 - }, - onUpdate: (value) => { - app.cores.style.modify({ - backgroundBlur: `${value}px` - }) - }, - storaged: false, - }, - { - id: "style.backgroundColorTransparency", - group: "aspect", - component: "Slider", - icon: "Eye", - title: "Background color transparency", - description: "Adjust the transparency of the background color.", - props: { - min: 0, - max: 1, - step: 0.1 - }, - defaultValue: () => { - const value = app.cores.style.getValue("backgroundColorTransparency") - - return value ? parseFloat(value) : 1 - }, - onUpdate: (value) => { - app.cores.style.modify({ - backgroundColorTransparency: value - }) - }, - storaged: false - }, - { - id: "style.backgroundSize", - group: "aspect", - component: "Select", - icon: "MdOutlineImageAspectRatio", - title: "Background size", - description: "Adjust the size of the background image.", - props: { - style: { - width: "100%" - }, - options: [ - { - label: "Cover", - value: "cover" - }, - { - label: "Contain", - value: "contain" - }, - { - label: "Auto", - value: "auto" - }, - { - label: "50%", - value: "50%" - }, - { - label: "100%", - value: "100%" - }, - { - label: "150%", - value: "150%" - }, - ] - }, - defaultValue: () => { - return app.cores.style.getValue("backgroundSize") - }, - onUpdate: (value) => { - app.cores.style.modify({ - backgroundSize: value - }) - - return value - }, - storaged: false - }, - { - id: "style.backgroundPosition", - group: "aspect", - component: "Select", - icon: "MdOutlineImageAspectRatio", - title: "Background position", - description: "Adjust the position of the background image.", - props: { - style: { - width: "100%" - }, - options: [ - { - label: "Left", - value: "left" - }, - { - label: "Center", - value: "center" - }, - { - label: "Right", - value: "right" - }, - { - label: "Top", - value: "top" - }, - ] - }, - defaultValue: () => { - return app.cores.style.getValue("backgroundPosition") - }, - onUpdate: (value) => { - app.cores.style.modify({ - backgroundPosition: value - }) - - return value - }, - storaged: false - }, - { - id: "style.backgroundRepeat", - group: "aspect", - component: "Select", - icon: "MdOutlineImageAspectRatio", - title: "Background repeat", - description: "Adjust the repeat of the background image.", - props: { - style: { - width: "100%" - }, - options: [ - { - label: "Repeat", - value: "repeat" - }, - { - label: "No repeat", - value: "no-repeat" - }, - { - label: "Repeat X", - value: "repeat-x" - }, - { - label: "Repeat Y", - value: "repeat-y" - }, - ] - }, - defaultValue: () => { - return app.cores.style.getValue("backgroundRepeat") - }, - onUpdate: (value) => { - app.cores.style.modify({ - backgroundRepeat: value - }) - - return value - }, - storaged: false - }, - { - id: "style.backgroundAttachment", - group: "aspect", - component: "Select", - icon: "MdOutlineImageAspectRatio", - title: "Background attachment", - description: "Adjust the attachment of the background image.", - props: { - style: { - width: "100%" - }, - options: [ - { - label: "Scroll", - value: "scroll" - }, - { - label: "Fixed", - value: "fixed" - }, - { - label: "Local", - value: "local" - }, - { - label: "Initial", - value: "initial" - }, - { - label: "Inherit", - value: "inherit" - }, - ] - }, - defaultValue: () => { - return app.cores.style.getValue("backgroundAttachment") - }, - onUpdate: (value) => { - app.cores.style.modify({ - backgroundAttachment: value - }) - - return value - }, + title: "Background tweaker", + description: "Tweak the custom background", + component: loadable(() => import("../components/backgroundTweaker")), storaged: false }, { id: "resetTheme", group: "aspect", component: "Button", - title: "Reset theme", + icon: "IoMdRefresh", + title: "Reset to default theme", props: { - children: "Default Theme" + children: "Reset" }, onUpdate: (value) => { Modal.confirm({ title: "Are you sure you want to reset the theme to the default theme ?", description: "This action will reset the theme to the default theme. All your modifications will be lost.", onOk: () => { - app.cores.style.setDefault() + app.cores.style.resetToDefault() } }) }, diff --git a/packages/app/src/settings/components/backgroundSelector/index.jsx b/packages/app/src/settings/components/backgroundSelector/index.jsx index 3519f3f8..111936e7 100755 --- a/packages/app/src/settings/components/backgroundSelector/index.jsx +++ b/packages/app/src/settings/components/backgroundSelector/index.jsx @@ -38,7 +38,7 @@ export default (props) => {
{ - app.cores.style.modify({ + app.cores.style.mutateTheme({ backgroundSVG: `url("${background.src}")` }) }} diff --git a/packages/app/src/settings/components/backgroundTweaker/index.jsx b/packages/app/src/settings/components/backgroundTweaker/index.jsx new file mode 100644 index 00000000..6df90362 --- /dev/null +++ b/packages/app/src/settings/components/backgroundTweaker/index.jsx @@ -0,0 +1,313 @@ +import React from "react" +import * as antd from "antd" +import classnames from "classnames" + +import { Icons } from "@components/Icons" + +import "./index.less" + +const Tweaks = { + blur: { + id: "style.backgroundBlur", + fetchValue: () => { + const value = app.cores.style.getVar("backgroundBlur") + + if (typeof value !== "string") { + return 0 + } + + return value ? parseInt(value.replace("px", "")) : 0 + }, + updateValue: (value) => { + app.cores.style.mutateTheme({ + backgroundBlur: `${value}px` + }) + }, + }, + opacity: { + id: "style.backgroundColorTransparency", + fetchValue: () => { + const value = app.cores.style.getVar("backgroundColorTransparency") + + return value ? parseFloat(value) : 1 + }, + updateValue: (value) => { + app.cores.style.mutateTheme({ + backgroundColorTransparency: value + }) + }, + }, + size: { + id: "style.backgroundSize", + fetchValue: () => { + return app.cores.style.getVar("backgroundSize") + }, + updateValue: (value) => { + app.cores.style.mutateTheme({ + backgroundSize: value + }) + }, + options: [ + { + label: "Cover", + value: "cover" + }, + { + label: "Contain", + value: "contain" + }, + { + label: "Auto", + value: "auto" + }, + { + label: "50%", + value: "50%" + }, + { + label: "100%", + value: "100%" + }, + { + label: "150%", + value: "150%" + }, + ], + }, + position: { + id: "style.backgroundPosition", + fetchValue: () => { + return app.cores.style.getVar("backgroundPosition") + }, + updateValue: (value) => { + app.cores.style.mutateTheme({ + backgroundPosition: value + }) + }, + options: [ + { + label: "Left", + value: "left" + }, + { + label: "Center", + value: "center" + }, + { + label: "Right", + value: "right" + }, + { + label: "Top", + value: "top" + }, + ], + }, + repeat: { + id: "style.backgroundRepeat", + fetchValue: () => { + return app.cores.style.getVar("backgroundRepeat") + }, + updateValue: (value) => { + app.cores.style.mutateTheme({ + backgroundRepeat: value + }) + }, + options: [ + { + label: "Repeat", + value: "repeat" + }, + { + label: "No repeat", + value: "no-repeat" + }, + { + label: "Repeat X", + value: "repeat-x" + }, + { + label: "Repeat Y", + value: "repeat-y" + }, + ], + } +} + +function useBackgroundTweakerValues() { + const [values, setValues] = React.useState({ + blur: Tweaks.blur.fetchValue(), + opacity: Tweaks.opacity.fetchValue(), + size: Tweaks.size.fetchValue(), + position: Tweaks.position.fetchValue(), + repeat: Tweaks.repeat.fetchValue(), + }) + + function changeOption(key, value) { + if (!Tweaks[key]) { + console.error(`Tweaker ${key} not found`) + return false + } + + Tweaks[key].updateValue(value) + + setValues((prev) => { + return { + ...prev, + [key]: value + } + }) + } + + return [values, changeOption] +} + +const BackgroundTweaker = (props) => { + const [values, updateOption] = useBackgroundTweakerValues() + + return
+
+
+
{ + updateOption("position", "top") + }} + > + +
+
+ +
+
{ + updateOption("position", "left") + }} + > + +
+
{ + updateOption("position", "center") + }} + > + +
+ +
+ +
+
{ + updateOption("position", "bottom") + }} + > + +
+
+
+ +
+
+

Blur

+ + `${value}px` + }} + defaultValue={values.blur} + onChangeComplete={(values) => { + updateOption("blur", values) + }} + /> +
+ +
+

Opacity

+ + `${value * 100}%` + }} + defaultValue={values.opacity} + onChangeComplete={(value) => { + updateOption("opacity", value) + }} + /> +
+ +
+

Repeat

+ + { + updateOption("repeat", value) + }} + options={Tweaks.repeat.options} + /> +
+ +
+

Size

+ + { + updateOption("size", value) + }} + options={Tweaks.size.options} + /> +
+ +
+
+} + +export default BackgroundTweaker \ No newline at end of file diff --git a/packages/app/src/settings/components/backgroundTweaker/index.less b/packages/app/src/settings/components/backgroundTweaker/index.less new file mode 100644 index 00000000..a9f55329 --- /dev/null +++ b/packages/app/src/settings/components/backgroundTweaker/index.less @@ -0,0 +1,111 @@ +@anchors-selector-size: 120px; + +.background-tweaker { + display: flex; + flex-direction: row; + + align-items: center; + + padding: 10px; + gap: 30px; + + .background-tweaker-anchors { + display: flex; + flex-direction: column; + + align-items: center; + justify-content: space-between; + + padding: 10px; + + width: @anchors-selector-size; + height: @anchors-selector-size; + min-width: @anchors-selector-size; + min-height: @anchors-selector-size; + + border: 1px var(--border-color) solid; + border-radius: 12px; + + .background-tweaker-anchors-row { + display: flex; + flex-direction: row; + + align-items: center; + justify-content: space-between; + + width: 100%; + + .background-tweaker-anchor { + display: flex; + flex-direction: row; + + justify-content: center; + align-items: center; + + width: fit-content; + + font-size: 1.2rem; + + padding: 5px; + + border-radius: 12px; + + transition: all 150ms ease-in-out; + + &.selected { + background-color: var(--background-color-primary); + } + + &:only-child { + width: 100%; + } + + p, + svg { + margin: 0; + } + } + } + } + + .background-tweaker-sliders { + display: flex; + flex-direction: column; + + width: 100%; + + gap: 8px; + + .background-tweaker-sliders-option { + display: flex; + flex-direction: column; + + gap: 5px; + + width: 100%; + + p { + margin: 0; + } + + .ant-slider { + margin: 0; + padding: 3px; + } + + p { + display: flex; + flex-direction: row; + + align-items: center; + + gap: 5px; + + svg { + margin: 0; + font-size: 1.2rem; + } + } + } + } +} \ No newline at end of file diff --git a/packages/app/src/settings/components/changePassword/index.jsx b/packages/app/src/settings/components/changePassword/index.jsx index 68279871..8ffa03c2 100755 --- a/packages/app/src/settings/components/changePassword/index.jsx +++ b/packages/app/src/settings/components/changePassword/index.jsx @@ -126,7 +126,7 @@ const ChangePasswordComponent = (props) => { export default (props) => { const onClick = () => { if (app.isMobile) { - return app.DrawerController.open("passwordChange", ChangePasswordComponent) + return app.layout.drawer.open("passwordChange", ChangePasswordComponent) } return app.layout.sidedrawer.open("passwordChange", ChangePasswordComponent) diff --git a/packages/app/src/settings/components/themeVariantSelector/index.jsx b/packages/app/src/settings/components/themeVariantSelector/index.jsx new file mode 100644 index 00000000..f908f2ce --- /dev/null +++ b/packages/app/src/settings/components/themeVariantSelector/index.jsx @@ -0,0 +1,49 @@ +import React from "react" +import classnames from "classnames" +import { createIconRender } from "@components/Icons" + +import "./index.less" + +const variants = [ + { + id: "light", + icon: "MdLightMode", + }, + { + id: "dark", + icon: "MdDarkMode", + }, + { + id: "auto", + icon: "MdAutoFixHigh", + }, +] + +const ThemeVariantSelector = (props) => { + const [selected, setSelected] = React.useState(app.cores.style.currentVariantKey) + + React.useEffect(() => { + app.cores.style.applyVariant(selected) + }, [selected]) + + return
+ { + variants.map((variant) => { + return
{ + setSelected(variant.id) + }} + > + { + createIconRender(variant.icon) + } +
+ }) + } +
+} + +export default ThemeVariantSelector \ No newline at end of file diff --git a/packages/app/src/settings/components/themeVariantSelector/index.less b/packages/app/src/settings/components/themeVariantSelector/index.less new file mode 100644 index 00000000..8f8cc413 --- /dev/null +++ b/packages/app/src/settings/components/themeVariantSelector/index.less @@ -0,0 +1,45 @@ +.__setting_theme_variant_selector { + display: flex; + flex-direction: row; + + align-items: center; + + border: 1px var(--border-color) solid; + border-radius: 12px; + + overflow: hidden; + + .__setting_theme_variant_selector-variant { + display: flex; + flex-direction: column; + + align-items: center; + justify-content: center; + + padding: 10px 20px; + + border-right: 1px var(--border-color) solid; + + transition: all 150ms ease-in-out; + + cursor: pointer; + + &.selected { + background-color: var(--background-color-primary); + } + + // if is the last child, remove border + &:last-child { + border-right: 0; + } + + &:hover { + background-color: var(--background-color-primary-2); + } + + svg { + font-size: 1.3rem; + margin: 0; + } + } +} \ No newline at end of file diff --git a/packages/app/src/settings/general/index.jsx b/packages/app/src/settings/general/index.jsx index 1f87b705..a1922d80 100755 --- a/packages/app/src/settings/general/index.jsx +++ b/packages/app/src/settings/general/index.jsx @@ -26,35 +26,19 @@ export default { emitEvent: "app:language_changes", }, { - id: "haptics:enabled", - storaged: true, + id: "app:lpm", group: "general", component: "Switch", - icon: "MdVibration", - title: "Haptic Feedback", - description: "Enable haptic feedback on touch events.", - desktop: false - }, - { - id: "longPressDelay", - storaged: true, - group: "general", - component: "Slider", - icon: "MdTimer", - title: "Long press delay", - description: "Set the delay before long press trigger is activated.", + icon: "MdSlowMotionVideo", + title: "Low performance mode", + description: "Enable low performance mode disabling all the animations and secondary features, boosting the app performance.", + emitEvent: "app:lpm_changed", props: { - min: 300, - max: 2000, - step: 100, - marks: { - 300: "0.3s", - 600: "0.6s", - 1000: "1s", - 1500: "1.5s", - 2000: "2s", - } - } + disabled: true + }, + storaged: true, + experimental: true, + disabled: true, }, { id: "clear_internal_storage", @@ -70,21 +54,6 @@ export default { }, noUpdate: true, }, - { - id: "app:lpm", - group: "general", - component: "Switch", - icon: "MdSlowMotionVideo", - title: "Low performance mode", - description: "Enable low performance mode disabling all the animations and secondary features, boosting the app performance.", - emitEvent: "app:lpm_changed", - props: { - disabled: true - }, - storaged: true, - experimental: true, - disabled: true, - }, { id: "ui.effects", storaged: true, diff --git a/packages/app/src/settings/tap_share/index.jsx b/packages/app/src/settings/tap_share/index.jsx index 9c6b2a5a..7f1a2c60 100755 --- a/packages/app/src/settings/tap_share/index.jsx +++ b/packages/app/src/settings/tap_share/index.jsx @@ -202,7 +202,7 @@ class OwnTags extends React.Component { return ownedTag.serial === tag.serialNumber }) - if (!ownedTag && app.DrawerController.drawersLength() === 0) { + if (!ownedTag && app.layout.drawer.drawersLength() === 0) { app.message.error("This tag is not registered or you don't have permission to edit it.") return false } @@ -260,7 +260,7 @@ class OwnTags extends React.Component { } const OpenTagEditor = ({ tag, onFinish = () => app.navigation.softReload() } = {}) => { - app.DrawerController.open("tag_register", RegisterNewTag, { + app.layout.drawer.open("tag_register", RegisterNewTag, { componentProps: { tagData: tag, onFinish: onFinish, diff --git a/packages/app/src/utils/checkUserIdIsSelf/index.js b/packages/app/src/utils/checkUserIdIsSelf/index.js new file mode 100644 index 00000000..db6a7292 --- /dev/null +++ b/packages/app/src/utils/checkUserIdIsSelf/index.js @@ -0,0 +1,3 @@ +export default (user_id) => { + return user_id === app.userData._id +} \ No newline at end of file diff --git a/packages/app/ssl/cert.pem b/packages/app/ssl/cert.pem new file mode 100644 index 00000000..2cf79ddd --- /dev/null +++ b/packages/app/ssl/cert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEPzCCAyegAwIBAgISBCm/J1yKDXCE/xdDr6sqPM+iMA0GCSqGSIb3DQEBCwUA +MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD +EwJSMzAeFw0yNDA0MDUxMTEyMTJaFw0yNDA3MDQxMTEyMTFaMBsxGTAXBgNVBAMM +ECoucmFnZXN0dWRpby5uZXQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARZElFg25Qr +1cfjFWI0vKzEDmMB81zOSHVm1AwwVxQyT/87XSaUHoLkzh48+ooWw6t95LqcPmSW +lGFdhgvGRRvdOsSN1HvosD2lBCB774PcssyqK7KXIZuTa1I/7nIso+6jggISMIIC +DjAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFFnUMpE9b+gZNNrt1DlYd3HxgsaIMB8G +A1UdIwQYMBaAFBQusxe3WFbLrlAJQOYfr52LFMLGMFUGCCsGAQUFBwEBBEkwRzAh +BggrBgEFBQcwAYYVaHR0cDovL3IzLm8ubGVuY3Iub3JnMCIGCCsGAQUFBzAChhZo +dHRwOi8vcjMuaS5sZW5jci5vcmcvMBsGA1UdEQQUMBKCECoucmFnZXN0dWRpby5u +ZXQwEwYDVR0gBAwwCjAIBgZngQwBAgEwggEEBgorBgEEAdZ5AgQCBIH1BIHyAPAA +dgB2/4g/Crb7lVHCYcz1h7o0tKTNuyncaEIKn+ZnTFo6dAAAAY6uLHZmAAAEAwBH +MEUCIAN0x8P1LfVpqQfqfrV0Xh2DFtgIDdHLp9eN9P82/4PAAiEAx5X0ATzVjO1L +3jkKYyzN2gGHcsqKXB9PFVWxsO6qJv0AdgAZmBBxCfDWUi4wgNKeP2S7g24ozPkP +Uo7u385KPxa0ygAAAY6uLHZGAAAEAwBHMEUCIHqQzpaQFH4c4RLCEbtf8esSUE8o ++4VgFZQ9mWB5PcU9AiEAjHibXgQ1zlVK5G8kgygJZgPptV8rJvjIZ8IfgZDxqucw +DQYJKoZIhvcNAQELBQADggEBAHvERBxB+2izDfHh8PaZwM+wvmqZ18+9kNzdc/ri ++I53zpra6PELo8VjlVV5R8C/B4q5CJQf/AyElQdAwzME+6F1j0JhVktkaRqgoHwD +Qi/BQkJlAVbVdhHctpi7ukDhzVEPzqZ3VJDaVEnAA6HhVctec44W/4JgALone10D +xr3T8IA77hsZK+bW+2IXoSUdjY++2muGz8xH5jGEALtSat6iidaJCdDCogEvUiGa +nek8LqGm0b5a7tsubK7qbJwxDsBixGm01tCIMa/7Px+gNrry2APg9VPCOrTVBmAH +8TRonOKw4IzKbCRVpfXlGMvL+Ezi7vrHYO1lMRbceVBPspc= +-----END CERTIFICATE----- diff --git a/packages/app/ssl/chain.pem b/packages/app/ssl/chain.pem new file mode 100644 index 00000000..43b222a6 --- /dev/null +++ b/packages/app/ssl/chain.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw +WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg +RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP +R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx +sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm +NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg +Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG +/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB +Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA +FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw +AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw +Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB +gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W +PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl +ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz +CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm +lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4 +avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2 +yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O +yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids +hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+ +HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv +MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX +nLRbwHOoq7hHwg== +-----END CERTIFICATE----- diff --git a/packages/app/ssl/fullchain.pem b/packages/app/ssl/fullchain.pem new file mode 100644 index 00000000..6c609bed --- /dev/null +++ b/packages/app/ssl/fullchain.pem @@ -0,0 +1,55 @@ +-----BEGIN CERTIFICATE----- +MIIEPzCCAyegAwIBAgISBCm/J1yKDXCE/xdDr6sqPM+iMA0GCSqGSIb3DQEBCwUA +MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD +EwJSMzAeFw0yNDA0MDUxMTEyMTJaFw0yNDA3MDQxMTEyMTFaMBsxGTAXBgNVBAMM +ECoucmFnZXN0dWRpby5uZXQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARZElFg25Qr +1cfjFWI0vKzEDmMB81zOSHVm1AwwVxQyT/87XSaUHoLkzh48+ooWw6t95LqcPmSW +lGFdhgvGRRvdOsSN1HvosD2lBCB774PcssyqK7KXIZuTa1I/7nIso+6jggISMIIC +DjAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFFnUMpE9b+gZNNrt1DlYd3HxgsaIMB8G +A1UdIwQYMBaAFBQusxe3WFbLrlAJQOYfr52LFMLGMFUGCCsGAQUFBwEBBEkwRzAh +BggrBgEFBQcwAYYVaHR0cDovL3IzLm8ubGVuY3Iub3JnMCIGCCsGAQUFBzAChhZo +dHRwOi8vcjMuaS5sZW5jci5vcmcvMBsGA1UdEQQUMBKCECoucmFnZXN0dWRpby5u +ZXQwEwYDVR0gBAwwCjAIBgZngQwBAgEwggEEBgorBgEEAdZ5AgQCBIH1BIHyAPAA +dgB2/4g/Crb7lVHCYcz1h7o0tKTNuyncaEIKn+ZnTFo6dAAAAY6uLHZmAAAEAwBH +MEUCIAN0x8P1LfVpqQfqfrV0Xh2DFtgIDdHLp9eN9P82/4PAAiEAx5X0ATzVjO1L +3jkKYyzN2gGHcsqKXB9PFVWxsO6qJv0AdgAZmBBxCfDWUi4wgNKeP2S7g24ozPkP +Uo7u385KPxa0ygAAAY6uLHZGAAAEAwBHMEUCIHqQzpaQFH4c4RLCEbtf8esSUE8o ++4VgFZQ9mWB5PcU9AiEAjHibXgQ1zlVK5G8kgygJZgPptV8rJvjIZ8IfgZDxqucw +DQYJKoZIhvcNAQELBQADggEBAHvERBxB+2izDfHh8PaZwM+wvmqZ18+9kNzdc/ri ++I53zpra6PELo8VjlVV5R8C/B4q5CJQf/AyElQdAwzME+6F1j0JhVktkaRqgoHwD +Qi/BQkJlAVbVdhHctpi7ukDhzVEPzqZ3VJDaVEnAA6HhVctec44W/4JgALone10D +xr3T8IA77hsZK+bW+2IXoSUdjY++2muGz8xH5jGEALtSat6iidaJCdDCogEvUiGa +nek8LqGm0b5a7tsubK7qbJwxDsBixGm01tCIMa/7Px+gNrry2APg9VPCOrTVBmAH +8TRonOKw4IzKbCRVpfXlGMvL+Ezi7vrHYO1lMRbceVBPspc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw +WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg +RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP +R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx +sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm +NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg +Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG +/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB +Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA +FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw +AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw +Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB +gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W +PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl +ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz +CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm +lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4 +avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2 +yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O +yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids +hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+ +HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv +MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX +nLRbwHOoq7hHwg== +-----END CERTIFICATE----- diff --git a/packages/app/ssl/privkey.pem b/packages/app/ssl/privkey.pem new file mode 100644 index 00000000..d4644cdc --- /dev/null +++ b/packages/app/ssl/privkey.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBKp+X11fOIu7rBPTij +4vj4a9M+pjkVA4/gzeDTEKmASArNd+6sdbRtBa4vdirWyU+hZANiAARZElFg25Qr +1cfjFWI0vKzEDmMB81zOSHVm1AwwVxQyT/87XSaUHoLkzh48+ooWw6t95LqcPmSW +lGFdhgvGRRvdOsSN1HvosD2lBCB774PcssyqK7KXIZuTa1I/7nIso+4= +-----END PRIVATE KEY----- diff --git a/packages/app/vite.config.js b/packages/app/vite.config.js index 88312070..050a4670 100755 --- a/packages/app/vite.config.js +++ b/packages/app/vite.config.js @@ -32,6 +32,10 @@ export default defineConfig({ fs: { allow: ["..", "../../"], }, + https: { + key: path.join(__dirname, "ssl", "privkey.pem"), + cert: path.join(__dirname, "ssl", "cert.pem"), + } }, css: { preprocessorOptions: { diff --git a/packages/server/classes/Languages/data.json b/packages/server/classes/Languages/data.json new file mode 100644 index 00000000..97ec78b2 --- /dev/null +++ b/packages/server/classes/Languages/data.json @@ -0,0 +1,611 @@ +{ + "ab": "Abkhazian", + "ace": "Achinese", + "ach": "Acoli", + "ada": "Adangme", + "ady": "Adyghe", + "aa": "Afar", + "afh": "Afrihili", + "af": "Afrikaans", + "agq": "Aghem", + "ain": "Ainu", + "ak": "Akan", + "akk": "Akkadian", + "bss": "Akoose", + "akz": "Alabama", + "sq": "Albanian", + "ale": "Aleut", + "arq": "Algerian Arabic", + "am": "Amarik", + "en_US": "American English", + "ase": "American Sign Language", + "egy": "Ancient Egyptian", + "grc": "Ancient Greek", + "anp": "Angika", + "njo": "Ao Naga", + "ar": "Arabik", + "an": "Aragonese", + "arc": "Aramaic", + "aro": "Araona", + "arp": "Arapaho", + "arw": "Arawak", + "hy": "Armenian", + "rup": "Aromanian", + "frp": "Arpitan", + "as": "Assamese", + "ast": "Asturian", + "asa": "Asu", + "cch": "Atsam", + "en_AU": "Australian English", + "de_AT": "Austrian German", + "av": "Avaric", + "ae": "Avestan", + "awa": "Awadhi", + "ay": "Aymara", + "az": "Azerbaijani", + "bfq": "Badaga", + "ksf": "Bafia", + "bfd": "Bafut", + "bqi": "Bakhtiari", + "ban": "Balinese", + "bal": "Baluchi", + "bm": "Bambara", + "bax": "Bamun", + "bjn": "Banjar", + "bas": "Basaa", + "ba": "Bashkir", + "eu": "Basque", + "bbc": "Batak Toba", + "bar": "Bavarian", + "bej": "Beja", + "be": "Belarus kasa", + "bem": "Bemba", + "bez": "Bena", + "bn": "Bengali kasa", + "bew": "Betawi", + "my": "B\u025b\u025bmis kasa", + "bho": "Bhojpuri", + "bik": "Bikol", + "bin": "Bini", + "bpy": "Bishnupriya", + "bi": "Bislama", + "byn": "Blin", + "zbl": "Blissymbols", + "brx": "Bodo", + "en": "Bor\u0254fo", + "bs": "Bosnian", + "bg": "B\u0254lgeria kasa", + "brh": "Brahui", + "bra": "Braj", + "pt_BR": "Brazilian Portuguese", + "br": "Breton", + "en_GB": "British English", + "bug": "Buginese", + "bum": "Bulu", + "bua": "Buriat", + "cad": "Caddo", + "frc": "Cajun French", + "en_CA": "Canadian English", + "fr_CA": "Canadian French", + "yue": "Cantonese", + "cps": "Capiznon", + "car": "Carib", + "ca": "Catalan", + "cay": "Cayuga", + "ceb": "Cebuano", + "tzm": "Central Atlas Tamazight", + "dtp": "Central Dusun", + "ckb": "Central Kurdish", + "esu": "Central Yupik", + "shu": "Chadian Arabic", + "chg": "Chagatai", + "ch": "Chamorro", + "ce": "Chechen", + "chr": "Cherokee", + "chy": "Cheyenne", + "chb": "Chibcha", + "cgg": "Chiga", + "qug": "Chimborazo Highland Quichua", + "chn": "Chinook Jargon", + "chp": "Chipewyan", + "cho": "Choctaw", + "cu": "Church Slavic", + "chk": "Chuukese", + "cv": "Chuvash", + "nwc": "Classical Newari", + "syc": "Classical Syriac", + "ksh": "Colognian", + "swb": "Comorian", + "swc": "Congo Swahili", + "cop": "Coptic", + "kw": "Cornish", + "co": "Corsican", + "cr": "Cree", + "mus": "Creek", + "crh": "Crimean Turkish", + "hr": "Croatian", + "dak": "Dakota", + "da": "Danish", + "dar": "Dargwa", + "dzg": "Dazaga", + "del": "Delaware", + "nl": "D\u025b\u025bkye", + "din": "Dinka", + "dv": "Divehi", + "doi": "Dogri", + "dgr": "Dogrib", + "dua": "Duala", + "dyu": "Dyula", + "dz": "Dzongkha", + "frs": "Eastern Frisian", + "efi": "Efik", + "arz": "Egyptian Arabic", + "eka": "Ekajuk", + "elx": "Elamite", + "ebu": "Embu", + "egl": "Emilian", + "myv": "Erzya", + "eo": "Esperanto", + "et": "Estonian", + "pt_PT": "European Portuguese", + "es_ES": "European Spanish", + "ee": "Ewe", + "ewo": "Ewondo", + "ext": "Extremaduran", + "fan": "Fang", + "fat": "Fanti", + "fo": "Faroese", + "hif": "Fiji Hindi", + "fj": "Fijian", + "fil": "Filipino", + "fi": "Finnish", + "nl_BE": "Flemish", + "fon": "Fon", + "gur": "Frafra", + "fr": "Fr\u025bnkye", + "fur": "Friulian", + "ff": "Fulah", + "gaa": "Ga", + "gag": "Gagauz", + "gl": "Galician", + "gan": "Gan Chinese", + "lg": "Ganda", + "gay": "Gayo", + "gba": "Gbaya", + "gez": "Geez", + "ka": "Georgian", + "aln": "Gheg Albanian", + "bbj": "Ghomala", + "glk": "Gilaki", + "gil": "Gilbertese", + "gom": "Goan Konkani", + "gon": "Gondi", + "gor": "Gorontalo", + "got": "Gothic", + "grb": "Grebo", + "el": "Greek kasa", + "gn": "Guarani", + "gu": "Gujarati", + "guz": "Gusii", + "gwi": "Gwich\u02bcin", + "de": "Gyaaman", + "jv": "Gyabanis kasa", + "ja": "Gyapan kasa", + "hai": "Haida", + "ht": "Haitian", + "hak": "Hakka Chinese", + "hu": "Hangri kasa", + "ha": "Hausa", + "haw": "Hawaiian", + "he": "Hebrew", + "hz": "Herero", + "hil": "Hiligaynon", + "hi": "Hindi", + "ho": "Hiri Motu", + "hit": "Hittite", + "hmn": "Hmong", + "hup": "Hupa", + "iba": "Iban", + "ibb": "Ibibio", + "is": "Icelandic", + "io": "Ido", + "ig": "Igbo", + "ilo": "Iloko", + "smn": "Inari Sami", + "id": "Indonihyia kasa", + "izh": "Ingrian", + "inh": "Ingush", + "ia": "Interlingua", + "ie": "Interlingue", + "iu": "Inuktitut", + "ik": "Inupiaq", + "ga": "Irish", + "it": "Italy kasa", + "jam": "Jamaican Creole English", + "kaj": "Jju", + "dyo": "Jola-Fonyi", + "jrb": "Judeo-Arabic", + "jpr": "Judeo-Persian", + "jut": "Jutish", + "kbd": "Kabardian", + "kea": "Kabuverdianu", + "kab": "Kabyle", + "kac": "Kachin", + "kgp": "Kaingang", + "kkj": "Kako", + "kl": "Kalaallisut", + "kln": "Kalenjin", + "xal": "Kalmyk", + "kam": "Kamba", + "km": "Kambodia kasa", + "kbl": "Kanembu", + "kn": "Kannada", + "kr": "Kanuri", + "kaa": "Kara-Kalpak", + "krc": "Karachay-Balkar", + "krl": "Karelian", + "ks": "Kashmiri", + "csb": "Kashubian", + "kaw": "Kawi", + "kk": "Kazakh", + "ken": "Kenyang", + "kha": "Khasi", + "kho": "Khotanese", + "khw": "Khowar", + "ki": "Kikuyu", + "kmb": "Kimbundu", + "krj": "Kinaray-a", + "kiu": "Kirmanjki", + "tlh": "Klingon", + "bkm": "Kom", + "kv": "Komi", + "koi": "Komi-Permyak", + "kg": "Kongo", + "kok": "Konkani", + "ko": "Korea kasa", + "kfo": "Koro", + "kos": "Kosraean", + "avk": "Kotava", + "khq": "Koyra Chiini", + "ses": "Koyraboro Senni", + "kpe": "Kpelle", + "kri": "Krio", + "kj": "Kuanyama", + "kum": "Kumyk", + "ku": "Kurdish", + "kru": "Kurukh", + "kut": "Kutenai", + "nmg": "Kwasio", + "zh": "Kyaena kasa", + "cs": "Ky\u025bk kasa", + "ky": "Kyrgyz", + "quc": "K\u02bciche\u02bc", + "lad": "Ladino", + "lah": "Lahnda", + "lkt": "Lakota", + "lam": "Lamba", + "lag": "Langi", + "lo": "Lao", + "ltg": "Latgalian", + "la": "Latin", + "es_419": "Latin American Spanish", + "lv": "Latvian", + "lzz": "Laz", + "lez": "Lezghian", + "lij": "Ligurian", + "li": "Limburgish", + "ln": "Lingala", + "lfn": "Lingua Franca Nova", + "lzh": "Literary Chinese", + "lt": "Lithuanian", + "liv": "Livonian", + "jbo": "Lojban", + "lmo": "Lombard", + "nds": "Low German", + "sli": "Lower Silesian", + "dsb": "Lower Sorbian", + "loz": "Lozi", + "lu": "Luba-Katanga", + "lua": "Luba-Lulua", + "lui": "Luiseno", + "smj": "Lule Sami", + "lun": "Lunda", + "luo": "Luo", + "lb": "Luxembourgish", + "luy": "Luyia", + "mde": "Maba", + "mk": "Macedonian", + "jmc": "Machame", + "mad": "Madurese", + "maf": "Mafa", + "mag": "Magahi", + "vmf": "Main-Franconian", + "mai": "Maithili", + "mak": "Makasar", + "mgh": "Makhuwa-Meetto", + "kde": "Makonde", + "mg": "Malagasy", + "ms": "Malay kasa", + "ml": "Malayalam", + "mt": "Maltese", + "mnc": "Manchu", + "mdr": "Mandar", + "man": "Mandingo", + "mni": "Manipuri", + "gv": "Manx", + "mi": "Maori", + "arn": "Mapuche", + "mr": "Marathi", + "chm": "Mari", + "mh": "Marshallese", + "mwr": "Marwari", + "mas": "Masai", + "mzn": "Mazanderani", + "byv": "Medumba", + "men": "Mende", + "mwv": "Mentawai", + "mer": "Meru", + "mgo": "Meta\u02bc", + "es_MX": "Mexican Spanish", + "mic": "Micmac", + "dum": "Middle Dutch", + "enm": "Middle English", + "frm": "Middle French", + "gmh": "Middle High German", + "mga": "Middle Irish", + "nan": "Min Nan Chinese", + "min": "Minangkabau", + "xmf": "Mingrelian", + "mwl": "Mirandese", + "lus": "Mizo", + "ar_001": "Modern Standard Arabic", + "moh": "Mohawk", + "mdf": "Moksha", + "ro_MD": "Moldavian", + "lol": "Mongo", + "mn": "Mongolian", + "mfe": "Morisyen", + "ary": "Moroccan Arabic", + "mos": "Mossi", + "mul": "Multiple Languages", + "mua": "Mundang", + "ttt": "Muslim Tat", + "mye": "Myene", + "naq": "Nama", + "na": "Nauru", + "nv": "Navajo", + "ng": "Ndonga", + "nap": "Neapolitan", + "new": "Newari", + "ne": "N\u025bpal kasa", + "sba": "Ngambay", + "nnh": "Ngiemboon", + "jgo": "Ngomba", + "yrl": "Nheengatu", + "nia": "Nias", + "niu": "Niuean", + "zxx": "No linguistic content", + "nog": "Nogai", + "nd": "North Ndebele", + "frr": "Northern Frisian", + "se": "Northern Sami", + "nso": "Northern Sotho", + "no": "Norwegian", + "nb": "Norwegian Bokm\u00e5l", + "nn": "Norwegian Nynorsk", + "nov": "Novial", + "nus": "Nuer", + "nym": "Nyamwezi", + "ny": "Nyanja", + "nyn": "Nyankole", + "tog": "Nyasa Tonga", + "nyo": "Nyoro", + "nzi": "Nzima", + "nqo": "N\u02bcKo", + "oc": "Occitan", + "oj": "Ojibwa", + "ang": "Old English", + "fro": "Old French", + "goh": "Old High German", + "sga": "Old Irish", + "non": "Old Norse", + "peo": "Old Persian", + "pro": "Old Proven\u00e7al", + "or": "Oriya", + "om": "Oromo", + "osa": "Osage", + "os": "Ossetic", + "ota": "Ottoman Turkish", + "pal": "Pahlavi", + "pfl": "Palatine German", + "pau": "Palauan", + "pi": "Pali", + "pam": "Pampanga", + "pag": "Pangasinan", + "pap": "Papiamento", + "ps": "Pashto", + "pdc": "Pennsylvania German", + "fa": "P\u025b\u025bhyia kasa", + "phn": "Phoenician", + "pcd": "Picard", + "pms": "Piedmontese", + "pdt": "Plautdietsch", + "pon": "Pohnpeian", + "pnt": "Pontic", + "pl": "P\u0254land kasa", + "pt": "P\u0254\u0254tugal kasa", + "prg": "Prussian", + "pa": "Pungyabi kasa", + "qu": "Quechua", + "ru": "Rahyia kasa", + "raj": "Rajasthani", + "rap": "Rapanui", + "rar": "Rarotongan", + "rw": "Rewanda kasa", + "rif": "Riffian", + "rgn": "Romagnol", + "rm": "Romansh", + "rom": "Romany", + "rof": "Rombo", + "ro": "Romenia kasa", + "root": "Root", + "rtm": "Rotuman", + "rug": "Roviana", + "rn": "Rundi", + "rue": "Rusyn", + "rwk": "Rwa", + "ssy": "Saho", + "sah": "Sakha", + "sam": "Samaritan Aramaic", + "saq": "Samburu", + "sm": "Samoan", + "sgs": "Samogitian", + "sad": "Sandawe", + "sg": "Sango", + "sbp": "Sangu", + "sa": "Sanskrit", + "sat": "Santali", + "sc": "Sardinian", + "sas": "Sasak", + "sdc": "Sassarese Sardinian", + "stq": "Saterland Frisian", + "saz": "Saurashtra", + "sco": "Scots", + "gd": "Scottish Gaelic", + "sly": "Selayar", + "sel": "Selkup", + "seh": "Sena", + "see": "Seneca", + "sr": "Serbian", + "sh": "Serbo-Croatian", + "srr": "Serer", + "sei": "Seri", + "ksb": "Shambala", + "shn": "Shan", + "sn": "Shona", + "ii": "Sichuan Yi", + "scn": "Sicilian", + "sid": "Sidamo", + "bla": "Siksika", + "szl": "Silesian", + "zh_Hans": "Simplified Chinese", + "sd": "Sindhi", + "si": "Sinhala", + "sms": "Skolt Sami", + "den": "Slave", + "sk": "Slovak", + "sl": "Slovenian", + "xog": "Soga", + "sog": "Sogdien", + "so": "Somalia kasa", + "snk": "Soninke", + "azb": "South Azerbaijani", + "nr": "South Ndebele", + "alt": "Southern Altai", + "sma": "Southern Sami", + "st": "Southern Sotho", + "es": "Spain kasa", + "srn": "Sranan Tongo", + "zgh": "Standard Moroccan Tamazight", + "suk": "Sukuma", + "sux": "Sumerian", + "su": "Sundanese", + "sus": "Susu", + "sw": "Swahili", + "ss": "Swati", + "sv": "Sweden kasa", + "fr_CH": "Swiss French", + "gsw": "Swiss German", + "de_CH": "Swiss High German", + "syr": "Syriac", + "shi": "Tachelhit", + "th": "Taeland kasa", + "tl": "Tagalog", + "ty": "Tahitian", + "dav": "Taita", + "tg": "Tajik", + "tly": "Talysh", + "tmh": "Tamashek", + "ta": "Tamil kasa", + "trv": "Taroko", + "twq": "Tasawaq", + "tt": "Tatar", + "te": "Telugu", + "ter": "Tereno", + "teo": "Teso", + "tet": "Tetum", + "tr": "T\u025b\u025bki kasa", + "bo": "Tibetan", + "tig": "Tigre", + "ti": "Tigrinya", + "tem": "Timne", + "tiv": "Tiv", + "tli": "Tlingit", + "tpi": "Tok Pisin", + "tkl": "Tokelau", + "to": "Tongan", + "fit": "Tornedalen Finnish", + "zh_Hant": "Traditional Chinese", + "tkr": "Tsakhur", + "tsd": "Tsakonian", + "tsi": "Tsimshian", + "ts": "Tsonga", + "tn": "Tswana", + "tcy": "Tulu", + "tum": "Tumbuka", + "aeb": "Tunisian Arabic", + "tk": "Turkmen", + "tru": "Turoyo", + "tvl": "Tuvalu", + "tyv": "Tuvinian", + "tw": "Twi", + "kcg": "Tyap", + "udm": "Udmurt", + "uga": "Ugaritic", + "uk": "Ukren kasa", + "umb": "Umbundu", + "und": "Unknown Language", + "hsb": "Upper Sorbian", + "ur": "Urdu kasa", + "ug": "Uyghur", + "uz": "Uzbek", + "vai": "Vai", + "ve": "Venda", + "vec": "Venetian", + "vep": "Veps", + "vi": "Vi\u025btnam kasa", + "vo": "Volap\u00fck", + "vro": "V\u00f5ro", + "vot": "Votic", + "vun": "Vunjo", + "wa": "Walloon", + "wae": "Walser", + "war": "Waray", + "wbp": "Warlpiri", + "was": "Washo", + "guc": "Wayuu", + "cy": "Welsh", + "vls": "West Flemish", + "fy": "Western Frisian", + "mrj": "Western Mari", + "wal": "Wolaytta", + "wo": "Wolof", + "wuu": "Wu Chinese", + "xh": "Xhosa", + "hsn": "Xiang Chinese", + "yav": "Yangben", + "yao": "Yao", + "yap": "Yapese", + "ybb": "Yemba", + "yi": "Yiddish", + "yo": "Yoruba", + "zap": "Zapotec", + "dje": "Zarma", + "zza": "Zaza", + "zea": "Zeelandic", + "zen": "Zenaga", + "za": "Zhuang", + "gbz": "Zoroastrian Dari", + "zu": "Zulu", + "zun": "Zuni" +} \ No newline at end of file diff --git a/packages/server/classes/Languages/index.js b/packages/server/classes/Languages/index.js new file mode 100644 index 00000000..fe05f9e1 --- /dev/null +++ b/packages/server/classes/Languages/index.js @@ -0,0 +1,11 @@ +import Data from "./data.json" + +export default class Languages { + get() { + return Data + } + + resolveName(code) { + return Data[code] + } +} \ No newline at end of file diff --git a/packages/server/db_models/musicLyrics/index.js b/packages/server/db_models/musicLyrics/index.js index 249d5804..577e4771 100644 --- a/packages/server/db_models/musicLyrics/index.js +++ b/packages/server/db_models/musicLyrics/index.js @@ -7,7 +7,7 @@ export default { required: true }, lrc: { - type: String, + type: Object, } } } \ No newline at end of file diff --git a/packages/server/db_models/track/index.js b/packages/server/db_models/track/index.js index 74c08bd3..744ba32f 100755 --- a/packages/server/db_models/track/index.js +++ b/packages/server/db_models/track/index.js @@ -2,6 +2,10 @@ export default { name: "Track", collection: "tracks", schema: { + source: { + type: String, + required: true, + }, title: { type: String, required: true, @@ -12,10 +16,6 @@ export default { artists: { type: Array, }, - source: { - type: String, - required: true, - }, metadata: { type: Object, }, @@ -27,20 +27,13 @@ export default { type: Boolean, default: true, }, + publish_date: { + type: Date, + }, cover: { type: String, default: "https://storage.ragestudio.net/comty-static-assets/default_song.png" }, - videoCanvas: { - type: String, - }, - spotifyId: { - type: String, - }, - lyricsEnabled: { - type: Boolean, - default: true, - }, publisher: { type: Object, required: true, diff --git a/packages/server/gateway/proxy.js b/packages/server/gateway/proxy.js index 4c9749f2..7dcaf4ab 100644 --- a/packages/server/gateway/proxy.js +++ b/packages/server/gateway/proxy.js @@ -1,15 +1,38 @@ -import http from "node:http" import httpProxy from "http-proxy" import defaults from "linebridge/src/server/defaults" import pkg from "../package.json" +import http from "node:http" +import https from "node:https" + +import fs from "node:fs" +import path from "node:path" + +function getHttpServerEngine(extraOptions = {}, handler = () => { }) { + const sslKey = path.resolve(process.cwd(), "ssl", "privkey.pem") + const sslCert = path.resolve(process.cwd(), "ssl", "cert.pem") + + if (fs.existsSync(sslKey) && fs.existsSync(sslCert)) { + return https.createServer( + { + key: fs.readFileSync(sslKey), + cert: fs.readFileSync(sslCert), + ...extraOptions + }, + handler + ) + } else { + return http.createServer(extraOptions, handler) + } +} + export default class Proxy { constructor() { this.proxys = new Map() this.wsProxys = new Map() - this.http = http.createServer(this.handleHttpRequest) + this.http = getHttpServerEngine({}, this.handleHttpRequest) this.http.on("upgrade", this.handleHttpUpgrade) } diff --git a/packages/server/old_g.js b/packages/server/old_g.js deleted file mode 100755 index dc6e41e4..00000000 --- a/packages/server/old_g.js +++ /dev/null @@ -1,467 +0,0 @@ -require("dotenv").config() - -import fs from "node:fs" -import path from "node:path" -import repl from "node:repl" -import { Transform } from "node:stream" -import ChildProcess from "node:child_process" -import { Observable } from "@gullerya/object-observer" -import chalk from "chalk" -import Spinnies from "spinnies" -import chokidar from "chokidar" -import IPCRouter from "linebridge/src/server/classes/IPCRouter" -import treeKill from "tree-kill" - -import { dots as DefaultSpinner } from "spinnies/spinners.json" -import getInternalIp from "./lib/getInternalIp" -import comtyAscii from "./gateway/ascii" -import pkg from "./package.json" - -import { onExit } from "signal-exit" - -import Proxy from "./gateway/proxy" - -const bootloaderBin = path.resolve(__dirname, "boot") -const servicesPath = path.resolve(__dirname, "services") - -async function scanServices() { - const finalServices = [] - - let services = fs.readdirSync(servicesPath) - - for await (let _path of services) { - _path = path.resolve(servicesPath, _path) - - if (fs.lstatSync(_path).isDirectory()) { - // search main file "*.service.*" (using regex) on the root of the service path - const mainFile = fs.readdirSync(_path).find((filename) => { - const regex = new RegExp(`^.*\.service\..*$`) - - return regex.test(filename) - }) - - if (mainFile) { - finalServices.push(path.resolve(_path, mainFile)) - } - } - } - - return finalServices -} - -let internal_proxy = new Proxy() -let allReady = false -let selectedProcessInstance = null -let internalIp = null -let services = null - -const spinnies = new Spinnies() - -const ipcRouter = global.ipcRouter = new IPCRouter() -const instancePool = global.instancePool = [] -const serviceFileReference = {} -const serviceRegistry = global.serviceRegistry = Observable.from({}) - -Observable.observe(serviceRegistry, (changes) => { - const { type, path, value } = changes[0] - - switch (type) { - case "update": { - //console.log(`Updated service | ${path} > ${value}`) - - //check if all services all ready - if (Object.values(serviceRegistry).every((service) => service.initialized)) { - handleAllReady() - } - - break - } - } -}) - -function detachInstanceStd(instance) { - if (instance.logs) { - instance.logs.stdout.unpipe(process.stdout) - instance.logs.stderr.unpipe(process.stderr) - } -} - -function attachInstanceStd(instance, { afterMsg } = {}) { - if (instance.logs) { - console.clear() - - if (afterMsg) { - console.log(afterMsg) - } - - instance.logs.stdout.pipe(process.stdout) - instance.logs.stderr.pipe(process.stderr) - } -} - -function attachAllInstancesStd() { - for (let instance of instancePool) { - attachInstanceStd(instance.instance) - } - - selectedProcessInstance = "all" -} - -function detachAllInstancesStd() { - for (let instance of instancePool) { - detachInstanceStd(instance.instance) - } - - selectedProcessInstance = null -} - -const relp_commands = - -async function getIgnoredFiles(cwd) { - // git check-ignore -- * - let output = await new Promise((resolve, reject) => { - ChildProcess.exec("git check-ignore -- *", { - cwd: cwd - }, (err, stdout) => { - if (err) { - resolve(``) - } - - resolve(stdout) - }) - }) - - output = output.split("\n").map((file) => { - return `**/${file.trim()}` - }) - - output = output.filter((file) => { - return file - }) - - return output -} - -async function handleAllReady() { - if (allReady) { - return false - } - - console.clear() - - allReady = true - - console.log(comtyAscii) - console.log(`🎉 All services[${services.length}] ready!\n`) - console.log(`USE: select , reboot, exit`) - - await internal_proxy.listen(9000, "0.0.0.0") - - await attachAllInstancesStd() -} - -// SERVICE WATCHER FUNCTIONS -async function handleNewServiceStarting(id) { - if (serviceRegistry[id].ready === false) { - spinnies.add(id, { - text: `📦 [${id}] Loading service...`, - spinner: DefaultSpinner - }) - } -} - -async function handleServiceStarted(id) { - serviceRegistry[id].initialized = true - - if (serviceRegistry[id].ready === false) { - if (spinnies.pick(id)) { - spinnies.succeed(id, { text: `[${id}][${serviceRegistry[id].index}] Ready` }) - } - } - - serviceRegistry[id].ready = true -} - -async function handleServiceExit(id, code, err) { - serviceRegistry[id].initialized = true - - if (serviceRegistry[id].ready === false) { - if (spinnies.pick(id)) { - spinnies.fail(id, { text: `[${id}][${serviceRegistry[id].index}] Failed with code ${code}` }) - } - } - - console.log(`[${id}] Exit with code ${code}`) - - // try to unregister from proxy - internal_proxy.unregisterAllFromService(id) - - serviceRegistry[id].ready = false -} - - -async function handleIPCData(service_id, msg) { - if (msg.type === "log") { - console.log(`[${service_id}] ${msg.message}`) - } - - if (msg.status === "ready") { - await handleServiceStarted(service_id) - } - - if (msg.type === "router:register") { - if (msg.data.path_overrides) { - for await (let pathOverride of msg.data.path_overrides) { - await internal_proxy.register({ - serviceId: service_id, - path: `/${pathOverride}`, - target: `http://${internalIp}:${msg.data.listen.port}/${pathOverride}`, - pathRewrite: { - [`^/${pathOverride}`]: "", - }, - }) - } - } else { - await internal_proxy.register({ - serviceId: service_id, - path: `/${service_id}`, - target: `http://${msg.data.listen.ip}:${msg.data.listen.port}`, - }) - } - } - - if (msg.type === "router:ws:register") { - await internal_proxy.register({ - serviceId: service_id, - path: `/${msg.data.namespace}`, - target: `http://${internalIp}:${msg.data.listen.port}/${msg.data.namespace}`, - pathRewrite: { - [`^/${msg.data.namespace}`]: "", - }, - ws: true, - }) - } -} - -function spawnService({ id, service, cwd }) { - handleNewServiceStarting(id) - - const instanceEnv = { - ...process.env, - lb_service: { - id: service.id, - index: service.index, - }, - } - - let instance = ChildProcess.fork(bootloaderBin, [service], { - detached: false, - silent: true, - cwd: cwd, - env: instanceEnv, - killSignal: "SIGKILL", - }) - - instance.reload = () => { - ipcRouter.unregister({ id, instance }) - - // try to unregister from proxy - internal_proxy.unregisterAllFromService(id) - - instance.kill() - - instance = spawnService({ id, service, cwd }) - - const instanceIndex = instancePool.findIndex((_instance) => _instance.id === id) - - if (instanceIndex !== -1) { - instancePool[instanceIndex].instance = instance - } - - // check if selectedProcessInstance - if (selectedProcessInstance) { - if (selectedProcessInstance === "all") { - attachInstanceStd(instance, { - afterMsg: "Reloading service...", - }) - } else { - detachInstanceStd(selectedProcessInstance.instance) - - //if the selected process is this service, reattach std - if (selectedProcessInstance.id === id) { - attachInstanceStd(instance, { - afterMsg: "Reloading service...", - }) - } - } - } - } - - instance.logs = { - stdout: createServiceLogTransformer({ id }), - stderr: createServiceLogTransformer({ id, color: "bgRed" }), - } - - instance.logs.stdout.history = [] - instance.logs.stderr.history = [] - - // push to buffer history - instance.stdout.pipe(instance.logs.stdout) - instance.stderr.pipe(instance.logs.stderr) - - instance.on("message", (data) => { - return handleIPCData(id, data) - }) - - instance.on("close", (code, err) => { - return handleServiceExit(id, code, err) - }) - - ipcRouter.register({ id, instance }) - - return instance -} - -function createServiceLogTransformer({ id, color = "bgCyan" }) { - return new Transform({ - transform(data, encoding, callback) { - callback(null, `${chalk[color](`[${id}]`)} > ${data.toString()}`) - } - }) -} - -async function main() { - internalIp = await getInternalIp() - - console.clear() - console.log(comtyAscii) - console.log(`\nRunning ${chalk.bgBlue(`${pkg.name}`)} | ${chalk.bgMagenta(`[v${pkg.version}]`)} | ${internalIp} \n\n\n`) - - services = await scanServices() - - if (services.length === 0) { - console.error("❌ No service found") - return process.exit(1) - } - - console.log(`📦 Found ${services.length} service(s)`) - - // create watchers - for await (let service of services) { - const instanceFile = path.basename(service) - const instanceBasePath = path.dirname(service) - - const { name: id, version } = require(path.resolve(instanceBasePath, "package.json")) - - serviceFileReference[instanceFile] = id - - serviceRegistry[id] = { - index: services.indexOf(service), - id: id, - version: version, - file: instanceFile, - cwd: instanceBasePath, - buffer: [], - ready: false, - } - } - - // create a new process of node for each service - for await (let service of services) { - const { id, version, cwd } = serviceRegistry[serviceFileReference[path.basename(service)]] - - const instance = spawnService({ id, service, cwd }) - - const serviceInstance = { - id, - version, - instance - } - - // push to pool - instancePool.push(serviceInstance) - - // if is NODE_ENV to development, start a file watcher for hot-reload - if (process.env.NODE_ENV === "development") { - const ignored = [ - ...await getIgnoredFiles(cwd), - "**/.cache/**", - "**/node_modules/**", - "**/dist/**", - "**/build/**", - ] - - chokidar.watch(cwd, { - ignored: ignored, - persistent: true, - ignoreInitial: true, - }).on("all", (event, path) => { - // find instance from pool - const instanceIndex = instancePool.findIndex((instance) => instance.id === id) - - console.log(event, path, instanceIndex) - - // reload - instancePool[instanceIndex].instance.reload() - }) - } - } - - repl.start({ - prompt: "> ", - useGlobal: true, - eval: (input, context, filename, callback) => { - let inputs = input.split(" ") - - // remove last \n from input - inputs[inputs.length - 1] = inputs[inputs.length - 1].replace(/\n/g, "") - - // find relp command - const command = inputs[0] - const args = inputs.slice(1) - - const command_fn = relp_commands.find((relp_command) => { - let exising = false - - if (Array.isArray(relp_command.aliases)) { - exising = relp_command.aliases.includes(command) - } - - if (relp_command.cmd === command) { - exising = true - } - - return exising - }) - - if (!command_fn) { - return callback(`Command not found: ${command}`) - } - - return command_fn.fn(callback, ...args) - } - }) - - onExit((code, signal) => { - console.clear() - console.log(`\n🛑 Preparing to exit...`) - - console.log(`Stoping proxy...`) - - internal_proxy.close() - - console.log(`Kill all ${instancePool.length} instances...`) - - for (let instance of instancePool) { - console.log(`Killing ${instance.id} [${instance.instance.pid}]`) - - instance.instance.kill() - - treeKill(instance.instance.pid) - } - - treeKill(process.pid) - }) -} - -main() \ No newline at end of file diff --git a/packages/server/services/chats/routes/chats/my/get.js b/packages/server/services/chats/routes/chats/my/get.js new file mode 100644 index 00000000..e82f7c79 --- /dev/null +++ b/packages/server/services/chats/routes/chats/my/get.js @@ -0,0 +1,77 @@ +import { User, ChatMessage } from "@db_models" + +export default { + middlewares: ["withAuthentication"], + fn: async (req) => { + // get recent messages + // query messages sended to current user or from current user + // must pick one message by user_id + + const current_user_id = req.auth.session.user_id + + let history = await ChatMessage.aggregate([ + { + $match: { + $or: [ + { from_user_id: current_user_id }, + { to_user_id: current_user_id } + ] + } + }, + { + $sort: { created_at: -1 } + }, + { + $group: { + _id: { + $cond: [ + { $eq: ["$from_user_id", current_user_id] }, + "$to_user_id", + "$from_user_id" + ] + }, + latestMessage: { $first: "$$ROOT" } + } + }, + { + $replaceRoot: { newRoot: "$latestMessage" } + } + ]) + + if (history.length === 0) { + return history + } + + history = history.map((message) => { + + // chose an user_id that is not the `current_user_id` + message.chat_user_id = message.from_user_id === current_user_id ? message.to_user_id : message.from_user_id + + return message + }) + + // order by created_at + history = history.sort((a, b) => { + return new Date(b.created_at) - new Date(a.created_at) + }) + + const userData = await User.find({ + _id: { + $in: history.map((message) => { + return message.chat_user_id + }) + } + }) + + + history = history.map((message) => { + message.user = userData.find((user) => { + return user._id.toString() === message.chat_user_id + }) + + return message + }) + + return history + }, +} \ No newline at end of file diff --git a/packages/server/services/chats/routes_ws/chat/send/message.js b/packages/server/services/chats/routes_ws/chat/send/message.js index 45f31767..d7394dab 100644 --- a/packages/server/services/chats/routes_ws/chat/send/message.js +++ b/packages/server/services/chats/routes_ws/chat/send/message.js @@ -1,12 +1,14 @@ import { ChatMessage } from "@db_models" export default async (socket, payload, engine) => { + if (!socket.userData) { + throw new OperationError(401, "Unauthorized") + } + const created_at = new Date().getTime() const [from_user_id, to_user_id] = [socket.userData._id, payload.to_user_id] - const targetSocket = await engine.find.socketByUserId(payload.to_user_id) - const wsMessageObj = { ...payload, created_at: created_at, @@ -24,7 +26,11 @@ export default async (socket, payload, engine) => { socket.emit("chat:receive:message", wsMessageObj) - if (targetSocket.emit) { + const targetSocket = await engine.find.socketByUserId(payload.to_user_id) + + console.log(targetSocket) + + if (targetSocket) { await targetSocket.emit("chat:receive:message", wsMessageObj) } diff --git a/packages/server/services/chats/routes_ws/chat/state/typing.js b/packages/server/services/chats/routes_ws/chat/state/typing.js index e8c12da7..9b12da3e 100644 --- a/packages/server/services/chats/routes_ws/chat/state/typing.js +++ b/packages/server/services/chats/routes_ws/chat/state/typing.js @@ -1,20 +1,17 @@ export default async (socket, payload, engine) => { + if (!socket.userData) { + throw new OperationError(401, "Unauthorized") + } + const from_user_id = socket.userData._id const { to_user_id, is_typing } = payload const targetSocket = await engine.find.socketByUserId(to_user_id) - if (targetSocket) { + if (targetSocket && targetSocket.emit) { await targetSocket.emit("chat:state:typing", { + from_user_id: from_user_id, is_typing: is_typing }) - - // socket.pendingFunctions.push("chats:state:typing") - - // setTimeout(() => { - // socket.emit("chats:state:typing", { - // is_typing: false, - // }) - // }, 5000) } } \ No newline at end of file diff --git a/packages/server/services/ems/routes/dispatch/post.js b/packages/server/services/ems/routes/dispatch/post.js index c5f01470..dc4e7f8e 100644 --- a/packages/server/services/ems/routes/dispatch/post.js +++ b/packages/server/services/ems/routes/dispatch/post.js @@ -1,16 +1,28 @@ +import templates from "../../templates" + export default { useContext: ["mailTransporter"], middlewares: ["withAuthentication"], fn: async (req, res) => { req.body = await req.urlencoded() - const { to, subject, body } = req.body + let { to, subject, body, template } = req.body + + if (template) { + if (!templates[template]) { + throw new OperationError(404, "Template not found") + } + + body = templates[template]({ + ...req.body + }) + } const mailOptions = { - from: "comty_no_reply@ragestudio.net", + from: process.env.SMTP_USERNAME, to: to, subject: subject, - text: body + html: body } console.log(mailOptions) diff --git a/packages/server/services/ems/templates/account_disabled/index.handlebars b/packages/server/services/ems/templates/account_disabled/index.handlebars new file mode 100644 index 00000000..2768e503 --- /dev/null +++ b/packages/server/services/ems/templates/account_disabled/index.handlebars @@ -0,0 +1,290 @@ + + + + + Comty + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ +
+ + + + + + +
+ + + + + + +
+
+
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + + + +
+
+

+ Hi + @{{username}} +

+
+
+
+ +
+
+ +
+ + + + + + +
+ +
+ + + + + + +
+
+

+ + Your account has been disabled. We will then communicate to you via email with more detail about this action. + +

+
+
+
+ +
+
+ + + +
+ + + \ No newline at end of file diff --git a/packages/server/services/ems/templates/index.js b/packages/server/services/ems/templates/index.js index 5d5404ec..a6f86801 100644 --- a/packages/server/services/ems/templates/index.js +++ b/packages/server/services/ems/templates/index.js @@ -4,6 +4,7 @@ import path from "node:path" import Handlebars from "handlebars" export default { + account_disabled: Handlebars.compile(fs.readFileSync(path.resolve(__dirname, "account_disabled/index.handlebars"), "utf-8")), new_login: Handlebars.compile(fs.readFileSync(path.resolve(__dirname, "new_login/index.handlebars"), "utf-8")), mfa_code: Handlebars.compile(fs.readFileSync(path.resolve(__dirname, "mfa_code/index.handlebars"), "utf-8")), password_recovery: Handlebars.compile(fs.readFileSync(path.resolve(__dirname, "password_recovery/index.handlebars"), "utf-8")), diff --git a/packages/server/services/music/package.json b/packages/server/services/music/package.json index 1334afe7..ca9a3a24 100755 --- a/packages/server/services/music/package.json +++ b/packages/server/services/music/package.json @@ -26,6 +26,7 @@ "morgan": "^1.10.0", "ms": "^2.1.3", "music-metadata": "^7.14.0", + "openai": "^4.47.2", "redis": "^4.6.6", "socket.io": "^4.5.4" } diff --git a/packages/server/services/music/routes/music/feed/get.js b/packages/server/services/music/routes/music/feed/get.js index a2aed60b..4957499a 100644 --- a/packages/server/services/music/routes/music/feed/get.js +++ b/packages/server/services/music/routes/music/feed/get.js @@ -1,13 +1,20 @@ import { MusicRelease, Track } from "@db_models" export default async (req) => { - const { limit = 10, trim = 0 } = req.query + const { limit = 10, trim = 0, order = "desc" } = req.query - let result = await MusicRelease.find({}) + const searchQuery = {} + + const total_length = await MusicRelease.count(searchQuery) + + let result = await MusicRelease.find(searchQuery) .limit(limit) .skip(trim) + .sort({ created_at: order === "desc" ? -1 : 1 }) return { + total_length: total_length, + has_more: total_length > trim + result.length, items: result, } } \ No newline at end of file diff --git a/packages/server/services/music/routes/music/lyrics/[track_id]/get.js b/packages/server/services/music/routes/music/lyrics/[track_id]/get.js index 89b14284..cdf9b040 100644 --- a/packages/server/services/music/routes/music/lyrics/[track_id]/get.js +++ b/packages/server/services/music/routes/music/lyrics/[track_id]/get.js @@ -1,4 +1,5 @@ import { TrackLyric } from "@db_models" +import axios from "axios" function parseTimeToMs(timeStr) { const [minutes, seconds, milliseconds] = timeStr.split(":") @@ -8,6 +9,7 @@ function parseTimeToMs(timeStr) { export default async (req) => { const { track_id } = req.params + let { translate_lang = "original" } = req.query let trackLyric = await TrackLyric.findOne({ track_id @@ -19,12 +21,43 @@ export default async (req) => { trackLyric = trackLyric.toObject() + if (typeof trackLyric.lrc === "object") { + trackLyric.available_langs = Object.entries(trackLyric.lrc).map(([key, value]) => { + return { + id: key, + name: key, + value: value + } + }) + + if (typeof trackLyric.lrc[translate_lang] === "undefined") { + translate_lang = "original" + } + + trackLyric.lang = translate_lang + + trackLyric.lrc = trackLyric.lrc[translate_lang] + + const { data } = await axios.get(trackLyric.lrc).catch((err) => { + return false + }) + + trackLyric.lrc = data + }else { + trackLyric.available_langs = [{ + id: "original", + name: "Original", + value: null + }] + } + if (trackLyric.lrc) { trackLyric.lrc = trackLyric.lrc.split("\n") trackLyric.lrc = trackLyric.lrc.map((line) => { const syncedLine = {} - syncedLine.time = line.match(/\[.*\]/)[0] + //syncedLine.time = line.match(/\[.*\]/)[0] + syncedLine.time = line.split(" ")[0] syncedLine.text = line.replace(syncedLine.time, "").trim() if (syncedLine.text === "") { diff --git a/packages/server/services/music/routes/music/releases/self/get.js b/packages/server/services/music/routes/music/releases/self/get.js index 186545f9..b838b553 100644 --- a/packages/server/services/music/routes/music/releases/self/get.js +++ b/packages/server/services/music/routes/music/releases/self/get.js @@ -1,4 +1,4 @@ -import { Playlist, Release, Track } from "@db_models" +import { MusicRelease, Track } from "@db_models" export default { middlewares: ["withAuthentication"], @@ -21,33 +21,13 @@ export default { } } - const playlistsCount = await Playlist.count(searchQuery) - const releasesCount = await Release.count(searchQuery) - - let total_length = playlistsCount + releasesCount - - let playlists = await Playlist.find(searchQuery) + let releases = await MusicRelease.find(searchQuery) .sort({ created_at: -1 }) .limit(limit) .skip(offset) - playlists = playlists.map((playlist) => { - playlist = playlist.toObject() - - playlist.type = "playlist" - - return playlist - }) - - let releases = await Release.find(searchQuery) - .sort({ created_at: -1 }) - .limit(limit) - .skip(offset) - - let result = [...playlists, ...releases] - if (req.query.resolveItemsData === "true") { - result = await Promise.all( + releases = await Promise.all( playlists.map(async playlist => { playlist.list = await Track.find({ _id: [...playlist.list], @@ -59,8 +39,8 @@ export default { } return { - total_length: total_length, - items: result, + total_length: await MusicRelease.count(searchQuery), + items: releases, } } } \ No newline at end of file diff --git a/packages/server/services/users/routes/users/[user_id]/roles/get.js b/packages/server/services/users/routes/users/[user_id]/roles/get.js new file mode 100644 index 00000000..908d7de2 --- /dev/null +++ b/packages/server/services/users/routes/users/[user_id]/roles/get.js @@ -0,0 +1,12 @@ +import Users from "@classes/users" + +export default { + middlewares: ["withOptionalAuthentication"], + fn: async (req) => { + const data = await Users.data({ + user_id: req.auth.session.user_id, + }) + + return data.roles + } +} \ No newline at end of file diff --git a/packages/server/services/users/routes/users/self/roles/get.js b/packages/server/services/users/routes/users/self/roles/get.js new file mode 100644 index 00000000..68dab220 --- /dev/null +++ b/packages/server/services/users/routes/users/self/roles/get.js @@ -0,0 +1,16 @@ +import Users from "@classes/users" + +export default { + middlewares: ["withAuthentication"], + fn: async (req) => { + const data = await Users.data({ + user_id: user_id, + }) + + if (!data) { + throw new OperationError(404, "User not found") + } + + return data.roles + } +} \ No newline at end of file diff --git a/packages/server/ssl/cert.pem b/packages/server/ssl/cert.pem new file mode 100644 index 00000000..2cf79ddd --- /dev/null +++ b/packages/server/ssl/cert.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEPzCCAyegAwIBAgISBCm/J1yKDXCE/xdDr6sqPM+iMA0GCSqGSIb3DQEBCwUA +MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD +EwJSMzAeFw0yNDA0MDUxMTEyMTJaFw0yNDA3MDQxMTEyMTFaMBsxGTAXBgNVBAMM +ECoucmFnZXN0dWRpby5uZXQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARZElFg25Qr +1cfjFWI0vKzEDmMB81zOSHVm1AwwVxQyT/87XSaUHoLkzh48+ooWw6t95LqcPmSW +lGFdhgvGRRvdOsSN1HvosD2lBCB774PcssyqK7KXIZuTa1I/7nIso+6jggISMIIC +DjAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFFnUMpE9b+gZNNrt1DlYd3HxgsaIMB8G +A1UdIwQYMBaAFBQusxe3WFbLrlAJQOYfr52LFMLGMFUGCCsGAQUFBwEBBEkwRzAh +BggrBgEFBQcwAYYVaHR0cDovL3IzLm8ubGVuY3Iub3JnMCIGCCsGAQUFBzAChhZo +dHRwOi8vcjMuaS5sZW5jci5vcmcvMBsGA1UdEQQUMBKCECoucmFnZXN0dWRpby5u +ZXQwEwYDVR0gBAwwCjAIBgZngQwBAgEwggEEBgorBgEEAdZ5AgQCBIH1BIHyAPAA +dgB2/4g/Crb7lVHCYcz1h7o0tKTNuyncaEIKn+ZnTFo6dAAAAY6uLHZmAAAEAwBH +MEUCIAN0x8P1LfVpqQfqfrV0Xh2DFtgIDdHLp9eN9P82/4PAAiEAx5X0ATzVjO1L +3jkKYyzN2gGHcsqKXB9PFVWxsO6qJv0AdgAZmBBxCfDWUi4wgNKeP2S7g24ozPkP +Uo7u385KPxa0ygAAAY6uLHZGAAAEAwBHMEUCIHqQzpaQFH4c4RLCEbtf8esSUE8o ++4VgFZQ9mWB5PcU9AiEAjHibXgQ1zlVK5G8kgygJZgPptV8rJvjIZ8IfgZDxqucw +DQYJKoZIhvcNAQELBQADggEBAHvERBxB+2izDfHh8PaZwM+wvmqZ18+9kNzdc/ri ++I53zpra6PELo8VjlVV5R8C/B4q5CJQf/AyElQdAwzME+6F1j0JhVktkaRqgoHwD +Qi/BQkJlAVbVdhHctpi7ukDhzVEPzqZ3VJDaVEnAA6HhVctec44W/4JgALone10D +xr3T8IA77hsZK+bW+2IXoSUdjY++2muGz8xH5jGEALtSat6iidaJCdDCogEvUiGa +nek8LqGm0b5a7tsubK7qbJwxDsBixGm01tCIMa/7Px+gNrry2APg9VPCOrTVBmAH +8TRonOKw4IzKbCRVpfXlGMvL+Ezi7vrHYO1lMRbceVBPspc= +-----END CERTIFICATE----- diff --git a/packages/server/ssl/chain.pem b/packages/server/ssl/chain.pem new file mode 100644 index 00000000..43b222a6 --- /dev/null +++ b/packages/server/ssl/chain.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw +WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg +RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP +R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx +sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm +NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg +Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG +/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB +Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA +FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw +AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw +Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB +gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W +PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl +ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz +CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm +lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4 +avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2 +yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O +yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids +hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+ +HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv +MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX +nLRbwHOoq7hHwg== +-----END CERTIFICATE----- diff --git a/packages/server/ssl/fullchain.pem b/packages/server/ssl/fullchain.pem new file mode 100644 index 00000000..6c609bed --- /dev/null +++ b/packages/server/ssl/fullchain.pem @@ -0,0 +1,55 @@ +-----BEGIN CERTIFICATE----- +MIIEPzCCAyegAwIBAgISBCm/J1yKDXCE/xdDr6sqPM+iMA0GCSqGSIb3DQEBCwUA +MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD +EwJSMzAeFw0yNDA0MDUxMTEyMTJaFw0yNDA3MDQxMTEyMTFaMBsxGTAXBgNVBAMM +ECoucmFnZXN0dWRpby5uZXQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARZElFg25Qr +1cfjFWI0vKzEDmMB81zOSHVm1AwwVxQyT/87XSaUHoLkzh48+ooWw6t95LqcPmSW +lGFdhgvGRRvdOsSN1HvosD2lBCB774PcssyqK7KXIZuTa1I/7nIso+6jggISMIIC +DjAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFFnUMpE9b+gZNNrt1DlYd3HxgsaIMB8G +A1UdIwQYMBaAFBQusxe3WFbLrlAJQOYfr52LFMLGMFUGCCsGAQUFBwEBBEkwRzAh +BggrBgEFBQcwAYYVaHR0cDovL3IzLm8ubGVuY3Iub3JnMCIGCCsGAQUFBzAChhZo +dHRwOi8vcjMuaS5sZW5jci5vcmcvMBsGA1UdEQQUMBKCECoucmFnZXN0dWRpby5u +ZXQwEwYDVR0gBAwwCjAIBgZngQwBAgEwggEEBgorBgEEAdZ5AgQCBIH1BIHyAPAA +dgB2/4g/Crb7lVHCYcz1h7o0tKTNuyncaEIKn+ZnTFo6dAAAAY6uLHZmAAAEAwBH +MEUCIAN0x8P1LfVpqQfqfrV0Xh2DFtgIDdHLp9eN9P82/4PAAiEAx5X0ATzVjO1L +3jkKYyzN2gGHcsqKXB9PFVWxsO6qJv0AdgAZmBBxCfDWUi4wgNKeP2S7g24ozPkP +Uo7u385KPxa0ygAAAY6uLHZGAAAEAwBHMEUCIHqQzpaQFH4c4RLCEbtf8esSUE8o ++4VgFZQ9mWB5PcU9AiEAjHibXgQ1zlVK5G8kgygJZgPptV8rJvjIZ8IfgZDxqucw +DQYJKoZIhvcNAQELBQADggEBAHvERBxB+2izDfHh8PaZwM+wvmqZ18+9kNzdc/ri ++I53zpra6PELo8VjlVV5R8C/B4q5CJQf/AyElQdAwzME+6F1j0JhVktkaRqgoHwD +Qi/BQkJlAVbVdhHctpi7ukDhzVEPzqZ3VJDaVEnAA6HhVctec44W/4JgALone10D +xr3T8IA77hsZK+bW+2IXoSUdjY++2muGz8xH5jGEALtSat6iidaJCdDCogEvUiGa +nek8LqGm0b5a7tsubK7qbJwxDsBixGm01tCIMa/7Px+gNrry2APg9VPCOrTVBmAH +8TRonOKw4IzKbCRVpfXlGMvL+Ezi7vrHYO1lMRbceVBPspc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw +WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg +RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP +R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx +sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm +NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg +Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG +/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB +Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA +FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw +AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw +Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB +gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W +PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl +ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz +CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm +lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4 +avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2 +yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O +yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids +hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+ +HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv +MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX +nLRbwHOoq7hHwg== +-----END CERTIFICATE----- diff --git a/packages/server/ssl/privkey.pem b/packages/server/ssl/privkey.pem new file mode 100644 index 00000000..d4644cdc --- /dev/null +++ b/packages/server/ssl/privkey.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBKp+X11fOIu7rBPTij +4vj4a9M+pjkVA4/gzeDTEKmASArNd+6sdbRtBa4vdirWyU+hZANiAARZElFg25Qr +1cfjFWI0vKzEDmMB81zOSHVm1AwwVxQyT/87XSaUHoLkzh48+ooWw6t95LqcPmSW +lGFdhgvGRRvdOsSN1HvosD2lBCB774PcssyqK7KXIZuTa1I/7nIso+4= +-----END PRIVATE KEY----- diff --git a/testExtensions/tidal/package.json b/testExtensions/tidal/package.json new file mode 100644 index 00000000..954c8c60 --- /dev/null +++ b/testExtensions/tidal/package.json @@ -0,0 +1,7 @@ +{ + "name": "Tidal API", + "description": "Tidal playback from Comty", + "license": "MIT", + "version": "0.1.0", + "main": "./src/index.js" +} \ No newline at end of file diff --git a/testExtensions/tidal/src/tidal.extension.js b/testExtensions/tidal/src/tidal.extension.js new file mode 100644 index 00000000..42d08bab --- /dev/null +++ b/testExtensions/tidal/src/tidal.extension.js @@ -0,0 +1,9 @@ +import Extension from "evite/src/extension" + +export default class Tidal extends Extension { + static dependencies = ["player"] + + async onInitialize() { + + } +} \ No newline at end of file