feat: remember current tab

This commit is contained in:
2025-04-27 05:56:04 +00:00
parent bf4c06ee2d
commit 6cc7a537b6
10 changed files with 86 additions and 41 deletions

View File

@ -1,3 +1,11 @@
## 0.16.0
- Fixed the clear cache button not working
- Fixed Linux builds
- Moved the store tab to the left
- STARTLINER now remembers the recently open tab and re-opens it on the next session
- Added "Beta" to the title as STARTLINER is approaching feature-completeness
## 0.15.0 ## 0.15.0
- Added internationalization - Added internationalization

View File

@ -336,7 +336,7 @@ async fn update(app: tauri::AppHandle) -> tauri_plugin_updater::Result<()> {
fn open_window(apph: AppHandle) -> anyhow::Result<()> { fn open_window(apph: AppHandle) -> anyhow::Result<()> {
let config = apph.config().clone(); let config = apph.config().clone();
tauri::WebviewWindowBuilder::new(&apph, "main", tauri::WebviewUrl::App("index.html".into())) tauri::WebviewWindowBuilder::new(&apph, "main", tauri::WebviewUrl::App("index.html".into()))
.title(format!("STARTLINER {}", config.version.unwrap_or_default())) .title(format!("STARTLINER {} Beta", config.version.unwrap_or_default()))
.inner_size(900f64, 600f64) .inner_size(900f64, 600f64)
.min_inner_size(900f64, 600f64) .min_inner_size(900f64, 600f64)
.build()?; .build()?;

View File

@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "STARTLINER", "productName": "STARTLINER",
"version": "0.15.0", "version": "0.16.0",
"identifier": "zip.patafour.startliner", "identifier": "zip.patafour.startliner",
"build": { "build": {
"beforeDevCommand": "bun run dev", "beforeDevCommand": "bun run dev",

View File

@ -20,7 +20,6 @@ import OptionList from './OptionList.vue';
import PatchList from './PatchList.vue'; import PatchList from './PatchList.vue';
import ProfileList from './ProfileList.vue'; import ProfileList from './ProfileList.vue';
import StartButton from './StartButton.vue'; import StartButton from './StartButton.vue';
import { invoke } from '../invoke';
import { import {
useClientStore, useClientStore,
useGeneralStore, useGeneralStore,
@ -36,10 +35,10 @@ const prf = usePrfStore();
const general = useGeneralStore(); const general = useGeneralStore();
const client = useClientStore(); const client = useClientStore();
client.load();
pkg.setupListeners(); pkg.setupListeners();
const currentTab: Ref<'users' | 'loc' | 'patches' | 'rmt' | 'cfg' | 'info'> =
ref('users');
const pkgSearchTerm = ref(''); const pkgSearchTerm = ref('');
const isProfileDisabled = computed(() => prf.current === null); const isProfileDisabled = computed(() => prf.current === null);
@ -60,7 +59,6 @@ onMounted(async () => {
await Promise.all([prf.reloadList(), prf.reload()]); await Promise.all([prf.reloadList(), prf.reload()]);
if (prf.current !== null) { if (prf.current !== null) {
currentTab.value = 'loc';
await pkg.reloadAll(); await pkg.reloadAll();
} }
@ -205,10 +203,11 @@ listen<DownloadingStatus>('download-progress', (event) => {
<Tabs <Tabs
lazy lazy
:value="currentTab" :value="client.currentTab"
v-on:update:value=" v-on:update:value="
(value) => { (value) => {
currentTab = value as any; client.currentTab = value as string;
client.save();
} }
" "
class="h-screen" class="h-screen"
@ -216,6 +215,13 @@ listen<DownloadingStatus>('download-progress', (event) => {
<div class="fixed w-full flex z-100"> <div class="fixed w-full flex z-100">
<TabList class="grow" :show-navigators="false"> <TabList class="grow" :show-navigators="false">
<Tab value="users"><div class="pi pi-home"></div></Tab> <Tab value="users"><div class="pi pi-home"></div></Tab>
<Tab
:disabled="
isProfileDisabled || pkg.networkStatus !== 'online'
"
value="rmt"
><div class="pi pi-download"></div
></Tab>
<Tab :disabled="isProfileDisabled" value="loc" <Tab :disabled="isProfileDisabled" value="loc"
><div class="pi pi-box"></div ><div class="pi pi-box"></div
></Tab> ></Tab>
@ -224,13 +230,7 @@ listen<DownloadingStatus>('download-progress', (event) => {
value="patches" value="patches"
><div class="pi pi-ticket"></div ><div class="pi pi-ticket"></div
></Tab> ></Tab>
<Tab
:disabled="
isProfileDisabled || pkg.networkStatus !== 'online'
"
value="rmt"
><div class="pi pi-download"></div
></Tab>
<Tab :disabled="isProfileDisabled" value="cfg" <Tab :disabled="isProfileDisabled" value="cfg"
><div class="pi pi-cog"></div ><div class="pi pi-cog"></div
></Tab> ></Tab>
@ -243,13 +243,17 @@ listen<DownloadingStatus>('download-progress', (event) => {
<div class="flex gap-4"> <div class="flex gap-4">
<div <div
class="flex" class="flex"
v-if="['loc', 'rmt', 'cfg'].includes(currentTab)" v-if="
['loc', 'rmt', 'cfg'].includes(
client.currentTab
)
"
> >
<InputIcon class="self-center mr-2"> <InputIcon class="self-center mr-2">
<i class="pi pi-search" /> <i class="pi pi-search" />
</InputIcon> </InputIcon>
<InputText <InputText
v-if="currentTab === 'cfg'" v-if="client.currentTab === 'cfg'"
style="min-width: 0; width: 25dvw" style="min-width: 0; width: 25dvw"
class="self-center" class="self-center"
size="small" size="small"
@ -342,7 +346,12 @@ listen<DownloadingStatus>('download-progress', (event) => {
<InfoPage /> <InfoPage />
</TabPanel> </TabPanel>
</TabPanels> </TabPanels>
<div v-if="currentTab === 'users' || currentTab === 'info'"> <div
v-if="
client.currentTab === 'users' ||
client.currentTab === 'info'
"
>
<img <img
v-if="prf.current?.meta.game === 'ongeki'" v-if="prf.current?.meta.game === 'ongeki'"
src="/sticker-ongeki.svg" src="/sticker-ongeki.svg"

View File

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { Ref, ref } from 'vue'; import { Ref, ref } from 'vue';
// import Select from 'primevue/select';
import * as path from '@tauri-apps/api/path'; import * as path from '@tauri-apps/api/path';
import OptionCategory from './OptionCategory.vue'; import OptionCategory from './OptionCategory.vue';
import PatchEntry from './PatchEntry.vue'; import PatchEntry from './PatchEntry.vue';
@ -32,8 +33,6 @@ invoke('list_patches', { target: prf.current!.data.sgt.target }).then(
target: amd, target: amd,
})) as Patch[]; })) as Patch[];
})(); })();
const errorMessage = t('patch.noneFound');
</script> </script>
<template> <template>
@ -49,7 +48,7 @@ const errorMessage = t('patch.noneFound');
/> />
<div v-if="gamePatches === null">{{ t('patch.loading') }}</div> <div v-if="gamePatches === null">{{ t('patch.loading') }}</div>
<div v-if="gamePatches !== null && gamePatches.length === 0"> <div v-if="gamePatches !== null && gamePatches.length === 0">
{{ errorMessage }} {{ t('patch.noneFound') }}
</div> </div>
</OptionCategory> </OptionCategory>
<OptionCategory title="amdaemon.exe" always-found> <OptionCategory title="amdaemon.exe" always-found>
@ -60,7 +59,20 @@ const errorMessage = t('patch.noneFound');
/> />
<div v-if="gamePatches === null">Loading...</div> <div v-if="gamePatches === null">Loading...</div>
<div v-if="amdPatches !== null && amdPatches.length === 0"> <div v-if="amdPatches !== null && amdPatches.length === 0">
{{ errorMessage }} {{ t('patch.noneFound') }}
<!-- <br />
<Select
class="mt-3"
style="width: 400px"
:options="[
{},
{},
]"
:placeholder="t('patch.forceLoad')"
size="small"
option-label="title"
option-value="value"
></Select> -->
</div> </div>
</OptionCategory> </OptionCategory>
</template> </template>

View File

@ -73,10 +73,12 @@ const promptDeleteProfile = async () => {
const dataExists = ref(false); const dataExists = ref(false);
path.join(general.dataDir, `profile-${props.p!.game}-${props.p!.name}`).then( general.dataDir.then((dataDir) =>
async (p) => { path
dataExists.value = await invoke('file_exists', { path: p }); .join(dataDir, `profile-${props.p!.game}-${props.p!.name}`)
} .then(async (p) => {
dataExists.value = await invoke('file_exists', { path: p });
})
); );
</script> </script>
@ -145,11 +147,15 @@ path.join(general.dataDir, `profile-${props.p!.game}-${props.p!.name}`).then(
class="self-center" class="self-center"
style="width: 2rem; height: 2rem" style="width: 2rem; height: 2rem"
@click=" @click="
path async () =>
.join(general.configDir, `profile-${p!.game}-${p!.name}`) path
.then(async (path) => { .join(
await invoke('open_file', { path }); await general.configDir,
}) `profile-${p!.game}-${p!.name}`
)
.then(async (path) => {
await invoke('open_file', { path });
})
" "
/> />
<Button <Button
@ -162,11 +168,15 @@ path.join(general.dataDir, `profile-${props.p!.game}-${props.p!.name}`).then(
class="self-center" class="self-center"
style="width: 2rem; height: 2rem" style="width: 2rem; height: 2rem"
@click=" @click="
path async () =>
.join(general.dataDir, `profile-${p!.game}-${p!.name}`) path
.then(async (path) => { .join(
await invoke('open_file', { path }); await general.dataDir,
}) `profile-${p!.game}-${p!.name}`
)
.then(async (path) => {
await invoke('open_file', { path });
})
" "
/> />
</div> </div>

View File

@ -194,9 +194,7 @@ const tryStart = () => {
} }
" "
/> />
<Lazy> <ContextMenu ref="menu" :model="menuItems" />
<ContextMenu ref="menu" :model="menuItems" />
</Lazy>
<Button <Button
v-if="startStatus === 'ready'" v-if="startStatus === 'ready'"
v-tooltip="disabledTooltip" v-tooltip="disabledTooltip"

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { Ref, computed, onMounted, ref } from 'vue'; import { Ref, computed, ref } from 'vue';
import InputNumber from 'primevue/inputnumber'; import InputNumber from 'primevue/inputnumber';
import Select from 'primevue/select'; import Select from 'primevue/select';
import SelectButton from 'primevue/selectbutton'; import SelectButton from 'primevue/selectbutton';

View File

@ -51,6 +51,7 @@ export default {
loading: 'Loading...', loading: 'Loading...',
noneFound: noneFound:
"No compatible patches found. Make sure you're using unpacked and unpatched files.", "No compatible patches found. Make sure you're using unpacked and unpatched files.",
forceLoad: 'Force load',
// Example patch name override // Example patch name override
'standard-no-encryption': 'No encryption', 'standard-no-encryption': 'No encryption',
}, },

View File

@ -322,7 +322,7 @@ export const usePrfStore = defineStore('prf', () => {
const generalStore = useGeneralStore(); const generalStore = useGeneralStore();
const configDir = computed(async () => { const configDir = computed(async () => {
return await path.join( return path.join(
await generalStore.configDir, await generalStore.configDir,
`profile-${current.value?.meta.game}-${current.value?.meta.name}` `profile-${current.value?.meta.game}-${current.value?.meta.name}`
); );
@ -374,6 +374,7 @@ export const useClientStore = defineStore('client', () => {
const theme: Ref<'light' | 'dark' | 'system'> = ref('system'); const theme: Ref<'light' | 'dark' | 'system'> = ref('system');
const onboarded: Ref<Game[]> = ref([]); const onboarded: Ref<Game[]> = ref([]);
const locale: Ref<Locale> = ref('en'); const locale: Ref<Locale> = ref('en');
const currentTab: Ref<string> = ref('');
const _scaleValue = (value: ScaleType) => const _scaleValue = (value: ScaleType) =>
value === 's' ? 1 : value === 'm' ? 1.25 : value === 'l' ? 1.5 : 2; value === 's' ? 1 : value === 'm' ? 1.25 : value === 'l' ? 1.5 : 2;
@ -440,6 +441,10 @@ export const useClientStore = defineStore('client', () => {
if (input.locale) { if (input.locale) {
locale.value = input.locale; locale.value = input.locale;
} }
if (input.currentTab) {
currentTab.value = input.currentTab;
}
await setLocale(locale.value); await setLocale(locale.value);
await setTheme(theme.value); await setTheme(theme.value);
} catch (e) { } catch (e) {
@ -478,6 +483,7 @@ export const useClientStore = defineStore('client', () => {
theme: theme.value, theme: theme.value,
onboarded: onboarded.value, onboarded: onboarded.value,
locale: locale.value, locale: locale.value,
currentTab: currentTab.value,
}) })
); );
}; };
@ -553,6 +559,7 @@ export const useClientStore = defineStore('client', () => {
locale, locale,
timeout, timeout,
scaleModel, scaleModel,
currentTab,
_scaleValue, _scaleValue,
scaleValue, scaleValue,
load, load,