Onboarding (#132)

* Initial onboarding

* Update OnboardingModal.vue

* Add finish

* Animation

* Automatic opening

* Move onboarding icon to outside of main appbar

* Run lint

* run fmt

* mostly finish

* Finish onboarding

* fix onboarding bug + linux build

* fix build again

* Add back window shadows

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Jai A <jai@modrinth.com>
This commit is contained in:
Adrian O.V
2023-06-20 22:03:59 -04:00
committed by GitHub
parent bd697a02f5
commit 8e5a0b8ae2
27 changed files with 635 additions and 248 deletions

View File

@@ -1,5 +1,5 @@
<script setup>
import { handleError, onMounted, ref, watch } from 'vue'
import { ref, watch } from 'vue'
import { RouterView, RouterLink, useRouter } from 'vue-router'
import {
HomeIcon,
@@ -26,35 +26,20 @@ import { type } from '@tauri-apps/api/os'
import { appWindow } from '@tauri-apps/api/window'
import { isDev } from '@/helpers/utils.js'
import mixpanel from 'mixpanel-browser'
import { saveWindowState, StateFlags } from 'tauri-plugin-window-state-api'
import OnboardingModal from '@/components/OnboardingModal.vue'
import { getVersion } from '@tauri-apps/api/app'
const themeStore = useTheming()
const isLoading = ref(true)
onMounted(async () => {
const { settings, collapsed_navigation } = await get().catch(handleError)
themeStore.setThemeState(settings)
themeStore.collapsedNavigation = collapsed_navigation
await warning_listener((e) =>
notificationsWrapper.value.addNotification({
title: 'Warning',
text: e.message,
type: 'warn',
})
)
if ((await type()) === 'Darwin') {
document.getElementsByTagName('html')[0].classList.add('mac')
} else {
document.getElementsByTagName('html')[0].classList.add('windows')
}
})
defineExpose({
initialize: async () => {
isLoading.value = false
const { theme, opt_out_analytics, collapsed_navigation, advanced_rendering } = await get()
const { theme, opt_out_analytics, collapsed_navigation, advanced_rendering, onboarded } =
await get()
const dev = await isDev()
const version = await getVersion()
themeStore.setThemeState(theme)
themeStore.collapsedNavigation = collapsed_navigation
@@ -64,10 +49,16 @@ defineExpose({
if (opt_out_analytics) {
mixpanel.opt_out_tracking()
}
mixpanel.track('Launched')
mixpanel.track('Launched', { version, dev, onboarded })
if (!dev) document.addEventListener('contextmenu', (event) => event.preventDefault())
if ((await type()) === 'Darwin') {
document.getElementsByTagName('html')[0].classList.add('mac')
} else {
document.getElementsByTagName('html')[0].classList.add('windows')
}
await warning_listener((e) =>
notificationsWrapper.value.addNotification({
title: 'Warning',
@@ -119,15 +110,23 @@ document.querySelector('body').addEventListener('click', function (e) {
target = target.parentElement
}
})
const accounts = ref(null)
</script>
<template>
<SplashScreen v-if="isLoading" app-loading />
<div v-else class="container">
<suspense>
<OnboardingModal ref="testModal" :accounts="accounts" />
</suspense>
<div class="nav-container" :class="{ expanded: !themeStore.collapsedNavigation }">
<div class="nav-section">
<suspense>
<AccountsCard ref="accounts" :expanded="!themeStore.collapsedNavigation" />
<AccountsCard
ref="accounts"
:mode="themeStore.collapsedNavigation ? 'small' : 'expanded'"
/>
</suspense>
<div class="pages-list">
<RouterLink
@@ -215,7 +214,16 @@ document.querySelector('body').addEventListener('click', function (e) {
<Button class="titlebar-button" icon-only @click="() => appWindow.toggleMaximize()">
<MaximizeIcon />
</Button>
<Button class="titlebar-button close" icon-only @click="() => appWindow.close()">
<Button
class="titlebar-button close"
icon-only
@click="
() => {
saveWindowState(StateFlags.ALL)
appWindow.close()
}
"
>
<XIcon />
</Button>
</section>
@@ -250,10 +258,11 @@ document.querySelector('body').addEventListener('click', function (e) {
}
.window-controls {
z-index: 20;
display: none;
flex-direction: row;
align-items: center;
gap: 0;
gap: 0.25rem;
.titlebar-button {
display: flex;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -4,4 +4,3 @@ export { default as KoFiIcon } from './kofi.svg'
export { default as PatreonIcon } from './patreon.svg'
export { default as PaypalIcon } from './paypal.svg'
export { default as OpenCollectiveIcon } from './opencollective.svg'
export { default as Default } from './default.png'

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +1,16 @@
<template>
<div
v-if="mode !== 'isolated'"
ref="button"
class="button-base avatar-button"
:class="{ expanded: expanded }"
@click="toggle()"
:class="{ expanded: mode === 'expanded' }"
@click="showCard = !showCard"
>
<Avatar :size="expanded ? 'xs' : 'sm'" :src="selectedAccount?.profile_picture ?? ''" />
<div v-show="expanded" class="avatar-text">
<Avatar
:size="mode === 'expanded' ? 'xs' : 'sm'"
:src="selectedAccount ? `https://mc-heads.net/avatar/${selectedAccount.id}/128` : ''"
/>
<div v-show="mode === 'expanded'" class="avatar-text">
<div class="text no-select">
{{ selectedAccount ? selectedAccount.username : 'Offline' }}
</div>
@@ -17,9 +21,14 @@
</div>
</div>
<transition name="fade">
<Card v-if="showCard" ref="card" class="account-card" :class="{ expanded: expanded }">
<Card
v-if="showCard || mode === 'isolated'"
ref="card"
class="account-card"
:class="{ expanded: mode === 'expanded', isolated: mode === 'isolated' }"
>
<div v-if="selectedAccount" class="selected account">
<Avatar size="xs" :src="selectedAccount.profile_picture" />
<Avatar size="xs" :src="`https://mc-heads.net/avatar/${selectedAccount.id}/128`" />
<div>
<h4>{{ selectedAccount.username }}</h4>
<p>Selected</p>
@@ -37,7 +46,7 @@
<div v-if="displayAccounts.length > 0" class="account-group">
<div v-for="account in displayAccounts" :key="account.id" class="account-row">
<Button class="option account" @click="setAccount(account)">
<Avatar :src="account.profile_picture" class="icon" />
<Avatar :src="`https://mc-heads.net/avatar/${account.id}/128`" class="icon" />
<p>{{ account.username }}</p>
</Button>
<Button v-tooltip="'Log out'" icon-only @click="logout(account.id)">
@@ -55,7 +64,7 @@
<script setup>
import { Avatar, Button, Card, PlusIcon, TrashIcon, UsersIcon, LogInIcon } from 'omorphia'
import { ref, defineProps, computed, onMounted, onBeforeUnmount } from 'vue'
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
import {
users,
remove_user,
@@ -68,51 +77,41 @@ import { handleError } from '@/store/state.js'
import mixpanel from 'mixpanel-browser'
defineProps({
expanded: {
type: Boolean,
mode: {
type: String,
required: true,
default: 'normal',
},
})
const settings = ref(await get().catch(handleError))
const emit = defineEmits(['change'])
const appendProfiles = (accounts) => {
return accounts.map((account) => {
return {
...account,
profile_picture: `https://mc-heads.net/avatar/${account.id}/128`,
}
})
const settings = ref({})
const accounts = ref([])
async function refreshValues() {
settings.value = await get().catch(handleError)
accounts.value = await users().catch(handleError)
}
const accounts = ref(await users().then(appendProfiles).catch(handleError))
defineExpose({
refreshValues,
})
await refreshValues()
const displayAccounts = computed(() =>
accounts.value.filter((account) => settings.value.default_user !== account.id)
)
const selectedAccount = ref(
const selectedAccount = computed(() =>
accounts.value.find((account) => account.id === settings.value.default_user)
)
const refreshValues = async () => {
accounts.value = await users().then(appendProfiles).catch(handleError)
selectedAccount.value = accounts.value.find(
(account) => account.id === settings.value.default_user
)
}
let showCard = ref(false)
let card = ref(null)
let button = ref(null)
const setAccount = async (account) => {
async function setAccount(account) {
settings.value.default_user = account.id
selectedAccount.value = account
await set(settings.value).catch(handleError)
emit('change')
}
const login = async () => {
async function login() {
const url = await authenticate_begin_flow().catch(handleError)
const window = new WebviewWindow('loginWindow', {
@@ -120,14 +119,6 @@ const login = async () => {
url: url,
})
window.once('tauri://created', function () {
console.log('webview created')
})
window.once('tauri://error', function (e) {
console.log('webview error', e)
})
const loggedIn = await authenticate_await_completion().catch(handleError)
await setAccount(loggedIn)
await refreshValues()
@@ -141,14 +132,15 @@ const logout = async (id) => {
if (!selectedAccount.value && accounts.value.length > 0) {
await setAccount(accounts.value[0])
await refreshValues()
} else {
emit('change')
}
mixpanel.track('AccountLogOut')
}
const toggle = () => {
showCard.value = !showCard.value
}
let showCard = ref(false)
let card = ref(null)
let button = ref(null)
const handleClickOutside = (event) => {
const elements = document.elementsFromPoint(event.clientX, event.clientY)
if (
@@ -219,6 +211,12 @@ onBeforeUnmount(() => {
&.expanded {
left: 13.5rem;
}
&.isolated {
position: relative;
left: 0;
top: 0;
}
}
.accounts-title {

View File

@@ -61,7 +61,6 @@ const hideContextMenu = () => {
}
const optionClicked = (option) => {
console.log('item check', item.value)
emit('option-clicked', {
item: item.value,
option: option,

View File

@@ -57,7 +57,7 @@
<script setup>
import { Button, Modal, XIcon, DownloadIcon, DropdownSelect, formatCategory } from 'omorphia'
import { add_project_from_version as installMod } from '@/helpers/profile'
import { defineExpose, ref } from 'vue'
import { ref } from 'vue'
import { handleError, useTheming } from '@/store/state.js'
import mixpanel from 'mixpanel-browser'

View File

@@ -1,6 +1,6 @@
<template>
<JavaDetectionModal ref="detectJavaModal" @submit="(val) => emit('update:modelValue', val)" />
<div class="toggle-setting">
<div class="toggle-setting" :class="{ compact }">
<input
autocomplete="off"
:disabled="props.disabled"
@@ -18,10 +18,7 @@
"
/>
<span class="installation-buttons">
<Button
:disabled="props.disabled"
@click="$refs.detectJavaModal.show(props.version, props.modelValue)"
>
<Button :disabled="props.disabled" @click="autoDetect">
<SearchIcon />
Auto detect
</Button>
@@ -48,11 +45,12 @@
<script setup>
import { Button, SearchIcon, PlayIcon, CheckIcon, XIcon, FolderSearchIcon } from 'omorphia'
import { get_jre } from '@/helpers/jre.js'
import { find_jre_17_jres, get_jre } from '@/helpers/jre.js'
import { ref } from 'vue'
import { open } from '@tauri-apps/api/dialog'
import JavaDetectionModal from '@/components/ui/JavaDetectionModal.vue'
import mixpanel from 'mixpanel-browser'
import { handleError } from '@/store/state.js'
const props = defineProps({
version: {
@@ -74,6 +72,10 @@ const props = defineProps({
required: false,
default: null,
},
compact: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['update:modelValue'])
@@ -117,6 +119,18 @@ async function handleJavaFileInput() {
emit('update:modelValue', result)
}
}
const detectJavaModal = ref(null)
async function autoDetect() {
if (!props.compact) {
detectJavaModal.value.show(props.version, props.modelValue)
} else {
let versions = await find_jre_17_jres().catch(handleError)
if (versions.length > 0) {
emit('update:modelValue', versions[0])
}
}
}
</script>
<style lang="scss" scoped>
@@ -131,12 +145,15 @@ async function handleJavaFileInput() {
justify-content: space-between;
align-items: center;
gap: 0.5rem;
&.compact {
flex-wrap: wrap;
}
}
.installation-buttons {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem;
margin: 0;

View File

@@ -47,18 +47,7 @@
<Card v-if="showCard === true" ref="card" class="info-card">
<div v-for="loadingBar in currentLoadingBars" :key="loadingBar.id" class="info-text">
<h3 class="info-title">
{{ loadingBar.bar_type.pack_name ?? 'Installing Modpack' }}
</h3>
<ProgressBar :progress="Math.floor(loadingBar.current)" />
<div class="row">{{ Math.floor(loadingBar.current) }}% {{ loadingBar.message }}</div>
</div>
</Card>
</transition>
<transition name="download">
<Card v-if="showCard === true" ref="card" class="info-card">
<div v-for="loadingBar in currentLoadingBars" :key="loadingBar.id" class="info-text">
<h3 class="info-title">
{{ loadingBar.bar_type.pack_name ?? 'Installing Modpack' }}
{{ loadingBar.title }}
</h3>
<ProgressBar :progress="Math.floor(loadingBar.current)" />
<div class="row">{{ Math.floor(loadingBar.current) }}% {{ loadingBar.message }}</div>
@@ -123,6 +112,7 @@ const profiles = ref(null)
const infoButton = ref(null)
const profileButton = ref(null)
const showCard = ref(false)
const showProfiles = ref(false)
const currentProcesses = ref(await getRunningProfiles().catch(handleError))
@@ -159,15 +149,25 @@ const goToTerminal = (path) => {
router.push(`/instance/${encodeURIComponent(path ?? selectedProfile.value.path)}/logs`)
}
const currentLoadingBars = ref(Object.values(await progress_bars_list().catch(handleError)))
const unlistenLoading = await loading_listener(async () => {
await refreshInfo()
})
const currentLoadingBars = ref([])
const refreshInfo = async () => {
const currentLoadingBarCount = currentLoadingBars.value.length
currentLoadingBars.value = Object.values(await progress_bars_list().catch(handleError))
currentLoadingBars.value = Object.values(await progress_bars_list().catch(handleError)).map(
(x) => {
if (x.bar_type.type === 'java_download') {
x.title = 'Downloading Java ' + x.bar_type.version
}
if (x.bar_type.profile_name) {
x.title = x.bar_type.profile_name
}
if (x.bar_type.pack_name) {
x.title = x.bar_type.pack_name
}
return x
}
)
if (currentLoadingBars.value.length === 0) {
showCard.value = false
} else if (currentLoadingBarCount < currentLoadingBars.value.length) {
@@ -175,6 +175,11 @@ const refreshInfo = async () => {
}
}
await refreshInfo()
const unlistenLoading = await loading_listener(async () => {
await refreshInfo()
})
const selectProfile = (profile) => {
selectedProfile.value = profile
showProfiles.value = false

View File

@@ -1,10 +1,13 @@
import { ofetch } from 'ofetch'
import { handleError } from '@/store/state.js'
import { getVersion } from '@tauri-apps/api/app'
export const useFetch = async (url, item) => {
try {
const version = await getVersion()
return await ofetch(url, {
headers: { 'User-Agent': 'modrinth/theseus (support@modrinth.com)' },
headers: { 'User-Agent': `modrinth/theseus/${version} (support@modrinth.com)` },
})
} catch (err) {
handleError({ message: `Error fetching ${item}` })

View File

@@ -52,12 +52,12 @@ export async function get_jre(path) {
// Autodetect Java globals, by searching the users computer.
// Returns a *NEW* JavaGlobals that can be put into Settings
export async function autodetect_java_globals(path) {
return await invoke('jre_autodetect_java_globals', { path })
export async function autodetect_java_globals() {
return await invoke('jre_autodetect_java_globals')
}
// Automatically installs specified java version
export async function jre_auto_install_java(javaVersion) {
export async function auto_install_java(javaVersion) {
return await invoke('jre_auto_install_java', { javaVersion })
}

View File

@@ -21,9 +21,7 @@ breadcrumbs.setRootContext({ name: 'Home', link: route.path })
const recentInstances = shallowRef([])
const getInstances = async () => {
console.log('aa')
const profiles = await list(true).catch(handleError)
console.log(profiles)
recentInstances.value = Object.values(profiles).sort((a, b) => {
return dayjs(b.metadata.last_played ?? 0).diff(dayjs(a.metadata.last_played ?? 0))
})

View File

@@ -221,7 +221,6 @@ const handleRightClick = (event) => {
}
const handleOptionsClick = async (args) => {
console.log(args)
switch (args.option) {
case 'play':
await startInstance('InstancePageContextMenu')

View File

@@ -192,7 +192,7 @@ import {
renderString,
} from 'omorphia'
import { releaseColor } from '@/helpers/utils'
import { ref, defineProps, watch, computed } from 'vue'
import { ref, watch, computed } from 'vue'
import { useRoute } from 'vue-router'
import { useBreadcrumbs } from '@/store/breadcrumbs'