2023-03-17 15:46:55 -04:00
< script setup >
2023-07-19 14:13:25 -07:00
import { computed , ref , watch } from 'vue'
import { RouterView , RouterLink , useRouter , useRoute } from 'vue-router'
2023-05-22 18:11:31 -07:00
import {
HomeIcon ,
SearchIcon ,
LibraryIcon ,
PlusIcon ,
SettingsIcon ,
Button ,
Notifications ,
2023-06-20 00:09:03 -04:00
XIcon ,
2023-05-22 18:11:31 -07:00
} from 'omorphia'
2023-06-19 14:59:06 -07:00
import { useLoading , useTheming } from '@/store/state'
2023-04-11 18:21:10 -04:00
import AccountsCard from '@/components/ui/AccountsCard.vue'
2023-04-24 22:22:10 -04:00
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
2023-04-21 17:45:50 -04:00
import { get } from '@/helpers/settings'
2023-04-26 23:19:37 -04:00
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
import RunningAppBar from '@/components/ui/RunningAppBar.vue'
2023-05-10 18:50:42 -04:00
import SplashScreen from '@/components/ui/SplashScreen.vue'
2023-05-10 11:19:01 -07:00
import ModrinthLoadingIndicator from '@/components/modrinth-loading-indicator'
2023-05-22 18:11:31 -07:00
import { useNotifications } from '@/store/notifications.js'
2023-08-04 19:51:46 -07:00
import { offline _listener , command _listener , warning _listener } from '@/helpers/events.js'
2023-06-20 00:09:03 -04:00
import { MinimizeIcon , MaximizeIcon } from '@/assets/icons'
import { type } from '@tauri-apps/api/os'
import { appWindow } from '@tauri-apps/api/window'
2023-08-04 20:33:50 -07:00
import { isDev , getOS , isOffline } from '@/helpers/utils.js'
2023-08-04 19:51:46 -07:00
import {
mixpanel _track ,
mixpanel _init ,
mixpanel _opt _out _tracking ,
mixpanel _is _loaded ,
} from '@/helpers/mixpanel'
2023-06-20 22:03:59 -04:00
import { saveWindowState , StateFlags } from 'tauri-plugin-window-state-api'
import { getVersion } from '@tauri-apps/api/app'
2023-07-21 17:59:34 -04:00
import { window as TauriWindow } from '@tauri-apps/api'
2023-06-30 08:11:32 -07:00
import { TauriEvent } from '@tauri-apps/api/event'
import { await _sync , check _safe _loading _bars _complete } from './helpers/state'
import { confirm } from '@tauri-apps/api/dialog'
2023-08-03 11:07:35 -04:00
import URLConfirmModal from '@/components/ui/URLConfirmModal.vue'
import StickyTitleBar from '@/components/ui/tutorial/StickyTitleBar.vue'
import OnboardingScreen from '@/components/ui/tutorial/OnboardingScreen.vue'
2023-03-17 15:46:55 -04:00
2023-04-05 10:18:04 -04:00
const themeStore = useTheming ( )
2023-08-03 11:07:35 -04:00
const urlModal = ref ( null )
2023-05-10 18:50:42 -04:00
const isLoading = ref ( true )
2023-08-04 19:51:46 -07:00
2023-08-04 20:33:50 -07:00
const videoPlaying = ref ( false )
const offline = ref ( false )
2023-08-03 11:07:35 -04:00
const showOnboarding = ref ( false )
const onboardingVideo = ref ( )
2023-05-10 18:50:42 -04:00
defineExpose ( {
initialize : async ( ) => {
isLoading . value = false
2023-08-04 23:38:34 -07:00
const { theme , opt _out _analytics , collapsed _navigation , advanced _rendering , fully _onboarded } =
2023-06-20 22:03:59 -04:00
await get ( )
2023-08-04 20:33:50 -07:00
const os = await getOS ( )
// video should play if the user is not on linux, and has not onboarded
2023-08-04 23:38:34 -07:00
videoPlaying . value = ! fully _onboarded && os !== 'Linux'
2023-06-19 14:59:06 -07:00
const dev = await isDev ( )
2023-06-20 22:03:59 -04:00
const version = await getVersion ( )
2023-08-04 23:38:34 -07:00
showOnboarding . value = ! fully _onboarded
2023-06-19 14:59:06 -07:00
2023-05-10 18:50:42 -04:00
themeStore . setThemeState ( theme )
2023-06-19 14:59:06 -07:00
themeStore . collapsedNavigation = collapsed _navigation
themeStore . advancedRendering = advanced _rendering
2023-08-04 19:51:46 -07:00
mixpanel _init ( '014c7d6a336d0efaefe3aca91063748d' , { debug : dev , persistence : 'localStorage' } )
2023-06-19 14:59:06 -07:00
if ( opt _out _analytics ) {
2023-08-04 19:51:46 -07:00
mixpanel _opt _out _tracking ( )
2023-06-19 14:59:06 -07:00
}
2023-08-04 23:38:34 -07:00
mixpanel _track ( 'Launched' , { version , dev , fully _onboarded } )
2023-06-19 14:59:06 -07:00
if ( ! dev ) document . addEventListener ( 'contextmenu' , ( event ) => event . preventDefault ( ) )
2023-06-20 22:03:59 -04:00
if ( ( await type ( ) ) === 'Darwin' ) {
document . getElementsByTagName ( 'html' ) [ 0 ] . classList . add ( 'mac' )
} else {
document . getElementsByTagName ( 'html' ) [ 0 ] . classList . add ( 'windows' )
}
2023-08-04 19:51:46 -07:00
offline . value = await isOffline ( )
await offline _listener ( ( b ) => {
offline . value = b
} )
2023-06-19 14:59:06 -07:00
await warning _listener ( ( e ) =>
notificationsWrapper . value . addNotification ( {
title : 'Warning' ,
text : e . message ,
type : 'warn' ,
} )
)
2023-08-03 11:07:35 -04:00
if ( showOnboarding . value ) {
onboardingVideo . value . play ( )
}
2023-05-10 18:50:42 -04:00
} ,
} )
2023-06-19 14:59:06 -07:00
2023-06-30 08:11:32 -07:00
const confirmClose = async ( ) => {
const confirmed = await confirm (
'An action is currently in progress. Are you sure you want to exit?' ,
{
title : 'Modrinth' ,
type : 'warning' ,
}
)
return confirmed
}
const handleClose = async ( ) => {
2023-08-03 10:29:58 -07:00
// State should respond immeiately if it's safe to close
// If not, code is deadlocked or worse, so wait 2 seconds and then ask the user to confirm closing
// (Exception: if the user is changing config directory, which takes control of the state, and it's taking a significant amount of time for some reason)
const isSafe = await Promise . race ( [
check _safe _loading _bars _complete ( ) ,
new Promise ( ( r ) => setTimeout ( r , 2000 ) ) ,
] )
2023-06-30 08:11:32 -07:00
if ( ! isSafe ) {
const response = await confirmClose ( )
if ( ! response ) {
return
}
}
await await _sync ( )
2023-07-21 17:59:34 -04:00
await TauriWindow . getCurrent ( ) . close ( )
2023-06-30 08:11:32 -07:00
}
2023-07-21 17:59:34 -04:00
TauriWindow . getCurrent ( ) . listen ( TauriEvent . WINDOW _CLOSE _REQUESTED , async ( ) => {
2023-06-30 08:11:32 -07:00
await handleClose ( )
} )
2023-06-19 14:59:06 -07:00
const router = useRouter ( )
router . afterEach ( ( to , from , failure ) => {
2023-08-04 19:51:46 -07:00
if ( mixpanel _is _loaded ( ) ) {
mixpanel _track ( 'PageView' , { path : to . path , fromPath : from . path , failed : failure } )
2023-06-19 14:59:06 -07:00
}
} )
2023-07-19 14:13:25 -07:00
const route = useRoute ( )
const isOnBrowse = computed ( ( ) => route . path . startsWith ( '/browse' ) )
2023-06-19 14:59:06 -07:00
2023-05-10 11:19:01 -07:00
const loading = useLoading ( )
2023-05-22 18:11:31 -07:00
const notifications = useNotifications ( )
const notificationsWrapper = ref ( null )
watch ( notificationsWrapper , ( ) => {
notifications . setNotifs ( notificationsWrapper . value )
} )
2023-06-02 07:09:46 -07:00
document . querySelector ( 'body' ) . addEventListener ( 'click' , function ( e ) {
let target = e . target
while ( target != null ) {
if ( target . matches ( 'a' ) ) {
if (
target . href &&
[ 'http://' , 'https://' , 'mailto:' , 'tel:' ] . some ( ( v ) => target . href . startsWith ( v ) ) &&
! target . classList . contains ( 'router-link-active' ) &&
! target . href . startsWith ( 'http://localhost' ) &&
! target . href . startsWith ( 'https://tauri.localhost' )
) {
window . _ _TAURI _INVOKE _ _ ( 'tauri' , {
_ _tauriModule : 'Shell' ,
message : {
cmd : 'open' ,
path : target . href ,
} ,
} )
}
2023-07-21 17:59:34 -04:00
e . preventDefault ( )
2023-06-02 07:09:46 -07:00
break
}
target = target . parentElement
}
} )
2023-06-20 22:03:59 -04:00
2023-08-14 13:23:42 -07:00
document . querySelector ( 'body' ) . addEventListener ( 'auxclick' , function ( e ) {
// disables middle click -> new tab
if ( e . button === 1 ) {
e . preventDefault ( )
// instead do a left click
const event = new MouseEvent ( 'click' , {
view : window ,
bubbles : true ,
cancelable : true ,
} )
e . target . dispatchEvent ( event )
}
} )
2023-06-20 22:03:59 -04:00
const accounts = ref ( null )
2023-08-03 11:07:35 -04:00
command _listener ( ( e ) => {
console . log ( e )
urlModal . value . show ( e )
} )
2023-03-17 15:46:55 -04:00
< / script >
2023-02-27 09:11:34 -07:00
< template >
2023-08-03 11:07:35 -04:00
< StickyTitleBar v-if = "videoPlaying" / >
< video
v - if = "videoPlaying"
ref = "onboardingVideo"
class = "video"
src = "@/assets/video.mp4"
autoplay
@ ended = "videoPlaying = false"
/ >
< SplashScreen v-else-if = "!videoPlaying && isLoading" app -loading / >
< OnboardingScreen v-else-if = "showOnboarding" :finish="() => (showOnboarding = false)" / >
2023-05-10 18:50:42 -04:00
< div v-else class = "container" >
2023-07-21 17:59:34 -04:00
< div class = "nav-container" >
2023-03-30 18:37:45 -04:00
< div class = "nav-section" >
2023-04-11 18:21:10 -04:00
< suspense >
2023-07-21 17:59:34 -04:00
< AccountsCard ref = "accounts" mode = "small" / >
2023-04-11 18:21:10 -04:00
< / suspense >
2023-03-17 15:46:55 -04:00
< div class = "pages-list" >
2023-08-14 17:02:22 -04:00
< RouterLink v-tooltip = "'Home'" to="/" class="btn icon-only collapsed-button" >
2023-05-05 18:37:23 -04:00
< HomeIcon / >
< / RouterLink >
< RouterLink
2023-08-14 17:02:22 -04:00
v - tooltip = "'Browse'"
2023-05-08 19:27:27 -04:00
to = "/browse/modpack"
2023-07-27 03:08:05 -04:00
class = "btn icon-only collapsed-button"
2023-05-05 18:37:23 -04:00
: class = " {
2023-07-19 14:13:25 -07:00
'router-link-active' : isOnBrowse ,
2023-05-05 18:37:23 -04:00
} "
>
< SearchIcon / >
< / RouterLink >
2023-08-14 17:02:22 -04:00
< RouterLink v-tooltip = "'Library'" to="/library" class="btn icon-only collapsed-button" >
2023-05-05 18:37:23 -04:00
< LibraryIcon / >
< / RouterLink >
2023-04-24 22:22:10 -04:00
< Suspense >
< InstanceCreationModal ref = "installationModal" / >
< / Suspense >
2023-03-17 15:46:55 -04:00
< / div >
< / div >
2023-03-30 18:37:45 -04:00
< div class = "settings pages-list" >
2023-05-16 22:25:00 -04:00
< Button
2023-08-14 17:02:22 -04:00
v - tooltip = "'Create profile'"
2023-08-03 11:07:35 -04:00
class = "sleek-primary collapsed-button"
icon - only
2023-08-04 19:51:46 -07:00
: disabled = "offline"
2023-05-16 22:25:00 -04:00
@ click = "() => $refs.installationModal.show()"
>
< PlusIcon / >
< / Button >
2023-08-14 17:02:22 -04:00
< RouterLink v-tooltip = "'Settings'" to="/settings" class="btn icon-only collapsed-button" >
2023-05-05 18:37:23 -04:00
< SettingsIcon / >
< / RouterLink >
2023-03-30 18:37:45 -04:00
< / div >
2023-03-17 15:46:55 -04:00
< / div >
2023-07-27 03:08:05 -04:00
< div class = "view" >
2023-07-21 17:59:34 -04:00
< div class = "appbar-row" >
< div data -tauri -drag -region class = "appbar" >
< section class = "navigation-controls" >
< Breadcrumbs data -tauri -drag -region / >
< / section >
< section class = "mod-stats" >
< Suspense >
2023-08-14 17:02:22 -04:00
< RunningAppBar / >
2023-07-21 17:59:34 -04:00
< / Suspense >
< / section >
< / div >
2023-06-20 00:09:03 -04:00
< section class = "window-controls" >
< Button class = "titlebar-button" icon -only @click ="() => appWindow.minimize()" >
< MinimizeIcon / >
< / Button >
< Button class = "titlebar-button" icon -only @click ="() => appWindow.toggleMaximize()" >
< MaximizeIcon / >
< / Button >
2023-06-20 22:03:59 -04:00
< Button
class = "titlebar-button close"
icon - only
@ click = "
( ) => {
saveWindowState ( StateFlags . ALL )
2023-06-30 08:11:32 -07:00
handleClose ( )
2023-06-20 22:03:59 -04:00
}
"
>
2023-06-20 00:09:03 -04:00
< XIcon / >
< / Button >
< / section >
2023-03-17 15:46:55 -04:00
< / div >
2023-03-30 18:37:45 -04:00
< div class = "router-view" >
2023-05-10 11:19:01 -07:00
< ModrinthLoadingIndicator
offset - height = "var(--appbar-height)"
offset - width = "var(--sidebar-width)"
/ >
2023-08-14 17:02:22 -04:00
< RouterView v-slot = "{ Component }" >
2023-05-09 19:48:47 -04:00
< template v-if = "Component" >
2023-05-10 11:19:01 -07:00
< Suspense @pending ="loading.startLoading()" @resolve ="loading.stopLoading()" >
2023-05-09 19:48:47 -04:00
< component :is = "Component" > < / component >
< / Suspense >
< / template >
< / RouterView >
2023-03-30 18:37:45 -04:00
< / div >
2023-03-17 15:46:55 -04:00
< / div >
2023-02-27 09:11:34 -07:00
< / div >
2023-08-03 11:07:35 -04:00
< URLConfirmModal ref = "urlModal" / >
< Notifications ref = "notificationsWrapper" / >
2023-02-27 09:11:34 -07:00
< / template >
2023-03-17 15:46:55 -04:00
< style lang = "scss" scoped >
2023-05-16 22:25:00 -04:00
. sleek - primary {
background - color : var ( -- color - brand - highlight ) ;
transition : all ease - in - out 0.1 s ;
}
2023-06-20 00:09:03 -04:00
. navigation - controls {
flex - grow : 1 ;
width : min - content ;
}
2023-07-21 17:59:34 -04:00
. appbar - row {
display : flex ;
flex - direction : row ;
}
2023-06-20 00:09:03 -04:00
. window - controls {
2023-06-20 22:03:59 -04:00
z - index : 20 ;
2023-06-20 00:09:03 -04:00
display : none ;
flex - direction : row ;
align - items : center ;
. titlebar - button {
display : flex ;
align - items : center ;
justify - content : center ;
cursor : pointer ;
transition : all ease - in - out 0.1 s ;
background - color : var ( -- color - raised - bg ) ;
color : var ( -- color - base ) ;
2023-07-21 17:59:34 -04:00
border - radius : 0 ;
height : 3.25 rem ;
2023-06-20 00:09:03 -04:00
& . close {
& : hover ,
& : active {
background - color : var ( -- color - red ) ;
color : var ( -- color - accent - contrast ) ;
}
}
& : hover ,
& : active {
background - color : var ( -- color - button - bg ) ;
color : var ( -- color - contrast ) ;
}
}
}
2023-03-17 15:46:55 -04:00
. container {
2023-05-10 11:19:01 -07:00
-- appbar - height : 3.25 rem ;
2023-06-20 00:09:03 -04:00
-- sidebar - width : 4.5 rem ;
2023-05-10 11:19:01 -07:00
2023-03-30 18:37:45 -04:00
height : 100 vh ;
display : flex ;
flex - direction : row ;
overflow : hidden ;
2023-03-17 15:46:55 -04:00
. view {
2023-05-10 11:19:01 -07:00
width : calc ( 100 % - var ( -- sidebar - width ) ) ;
2023-09-05 00:15:33 +03:00
background - color : var ( -- color - raised - bg ) ;
2023-05-10 11:19:01 -07:00
2023-03-17 15:46:55 -04:00
. appbar {
display : flex ;
align - items : center ;
2023-07-21 17:59:34 -04:00
flex - grow : 1 ;
2023-06-20 00:09:03 -04:00
background : var ( -- color - raised - bg ) ;
2023-03-30 18:37:45 -04:00
text - align : center ;
2023-06-20 00:09:03 -04:00
padding : var ( -- gap - md ) ;
2023-04-26 23:19:37 -04:00
height : 3.25 rem ;
2023-06-20 00:09:03 -04:00
gap : var ( -- gap - sm ) ;
//no select
user - select : none ;
- webkit - user - select : none ;
2023-03-17 15:46:55 -04:00
}
2023-04-21 17:45:50 -04:00
. router - view {
width : 100 % ;
2023-04-26 23:19:37 -04:00
height : calc ( 100 % - 3.125 rem ) ;
2023-04-21 17:45:50 -04:00
overflow : auto ;
overflow - x : hidden ;
2023-06-20 00:09:03 -04:00
background - color : var ( -- color - bg ) ;
2023-09-05 00:15:33 +03:00
border - top - left - radius : var ( -- radius - xl ) ;
2023-04-21 17:45:50 -04:00
}
}
}
2023-03-17 15:46:55 -04:00
. nav - container {
display : flex ;
flex - direction : column ;
align - items : center ;
2023-03-30 18:37:45 -04:00
justify - content : space - between ;
height : 100 % ;
2023-06-20 00:09:03 -04:00
background - color : var ( -- color - raised - bg ) ;
2023-03-17 15:46:55 -04:00
box - shadow : var ( -- shadow - inset - sm ) , var ( -- shadow - floating ) ;
2023-06-20 00:09:03 -04:00
padding : var ( -- gap - md ) ;
2023-03-17 15:46:55 -04:00
}
2023-03-30 18:37:45 -04:00
2023-03-17 15:46:55 -04:00
. pages - list {
display : flex ;
flex - direction : column ;
2023-05-05 18:37:23 -04:00
align - items : flex - start ;
2023-03-17 15:46:55 -04:00
justify - content : flex - start ;
width : 100 % ;
2023-03-30 18:37:45 -04:00
gap : 0.5 rem ;
2023-03-17 15:46:55 -04:00
a {
display : flex ;
align - items : center ;
word - spacing : 3 px ;
background : inherit ;
transition : all ease - in - out 0.1 s ;
2023-03-30 18:37:45 -04:00
color : var ( -- color - base ) ;
2023-06-20 00:09:03 -04:00
box - shadow : none ;
2023-03-17 15:46:55 -04:00
& . router - link - active {
2023-04-21 17:45:50 -04:00
color : var ( -- color - contrast ) ;
2023-03-17 15:46:55 -04:00
background : var ( -- color - button - bg ) ;
2023-05-05 18:37:23 -04:00
box - shadow : var ( -- shadow - floating ) ;
2023-03-17 15:46:55 -04:00
}
& : hover {
background - color : var ( -- color - button - bg ) ;
color : var ( -- color - contrast ) ;
box - shadow : 0 px 4 px 4 px rgba ( 0 , 0 , 0 , 0.25 ) ;
text - decoration : none ;
}
2023-03-30 18:37:45 -04:00
}
2023-05-05 18:37:23 -04:00
& . primary {
color : var ( -- color - accent - contrast ) ;
background - color : var ( -- color - brand ) ;
}
2023-03-30 18:37:45 -04:00
}
2023-03-17 15:46:55 -04:00
2023-05-05 18:37:23 -04:00
. collapsed - button {
height : 3 rem ! important ;
width : 3 rem ! important ;
2023-03-30 15:56:19 -07:00
padding : 0.75 rem ;
2023-03-30 18:37:45 -04:00
border - radius : var ( -- radius - md ) ;
2023-05-05 18:37:23 -04:00
box - shadow : none ;
2023-03-30 18:37:45 -04:00
svg {
2023-05-05 18:37:23 -04:00
width : 1.5 rem ! important ;
height : 1.5 rem ! important ;
max - width : 1.5 rem ! important ;
max - height : 1.5 rem ! important ;
2023-03-30 18:37:45 -04:00
}
2023-05-05 18:37:23 -04:00
}
2023-03-30 18:37:45 -04:00
2023-03-17 15:46:55 -04:00
. instance - list {
display : flex ;
flex - direction : column ;
justify - content : center ;
width : 70 % ;
margin : 0.4 rem ;
p : nth - child ( 1 ) {
font - size : 0.6 rem ;
}
& > p {
color : var ( -- color - base ) ;
margin : 0.8 rem 0 ;
font - size : 0.7 rem ;
line - height : 0.8125 rem ;
font - weight : 500 ;
text - transform : uppercase ;
}
}
. user - section {
display : flex ;
justify - content : flex - start ;
align - items : center ;
width : 100 % ;
height : 4.375 rem ;
section {
display : flex ;
flex - direction : column ;
justify - content : flex - start ;
text - align : left ;
margin - left : 0.5 rem ;
}
. username {
margin - bottom : 0.3 rem ;
font - weight : 400 ;
line - height : 1.25 rem ;
color : var ( -- color - contrast ) ;
}
a {
font - weight : 400 ;
color : var ( -- color - secondary ) ;
}
}
2023-03-30 18:37:45 -04:00
. nav - section {
display : flex ;
flex - direction : column ;
justify - content : flex - start ;
align - items : center ;
width : 100 % ;
height : 100 % ;
gap : 1 rem ;
}
2023-08-03 11:07:35 -04:00
. video {
margin - top : 2.25 rem ;
width : 100 vw ;
height : calc ( 100 vh - 2.25 rem ) ;
object - fit : cover ;
border - radius : var ( -- radius - md ) ;
}
2023-03-17 15:46:55 -04:00
< / style >