Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
0add9200a6 | |||
ee49da3665 |
@ -1,3 +1,7 @@
|
|||||||
|
## 0.18.0
|
||||||
|
|
||||||
|
- Added new grouping options to the package list
|
||||||
|
|
||||||
## 0.17.0
|
## 0.17.0
|
||||||
|
|
||||||
- Added a package creation prompt
|
- Added a package creation prompt
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "STARTLINER",
|
"productName": "STARTLINER",
|
||||||
"version": "0.17.0",
|
"version": "0.18.0",
|
||||||
"identifier": "zip.patafour.startliner",
|
"identifier": "zip.patafour.startliner",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "bun run dev",
|
"beforeDevCommand": "bun run dev",
|
||||||
|
@ -4,13 +4,15 @@ import Button from 'primevue/button';
|
|||||||
import Dialog from 'primevue/dialog';
|
import Dialog from 'primevue/dialog';
|
||||||
import Fieldset from 'primevue/fieldset';
|
import Fieldset from 'primevue/fieldset';
|
||||||
import InputText from 'primevue/inputtext';
|
import InputText from 'primevue/inputtext';
|
||||||
|
import MultiSelect from 'primevue/multiselect';
|
||||||
import SelectButton from 'primevue/selectbutton';
|
import SelectButton from 'primevue/selectbutton';
|
||||||
import ToggleSwitch from 'primevue/toggleswitch';
|
import ToggleSwitch from 'primevue/toggleswitch';
|
||||||
|
import { emit } from '@tauri-apps/api/event';
|
||||||
import ModListEntry from './ModListEntry.vue';
|
import ModListEntry from './ModListEntry.vue';
|
||||||
import ModTitlecard from './ModTitlecard.vue';
|
import ModTitlecard from './ModTitlecard.vue';
|
||||||
import { invoke } from '../invoke';
|
import { invoke } from '../invoke';
|
||||||
import { useClientStore, usePkgStore, usePrfStore } from '../stores';
|
import { useClientStore, usePkgStore, usePrfStore } from '../stores';
|
||||||
import { Game, Package } from '../types';
|
import { Feature, Game, Package } from '../types';
|
||||||
import { pkgKey } from '../util';
|
import { pkgKey } from '../util';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
@ -35,32 +37,105 @@ const loadPackages = () => {
|
|||||||
|
|
||||||
loadPackages();
|
loadPackages();
|
||||||
|
|
||||||
const group = computed(() => {
|
const allCategories = computed(() => {
|
||||||
const grouped = Object.groupBy(
|
const res = new Set<string>();
|
||||||
pkgs.allLocal
|
for (const pkg of pkgs.allLocal) {
|
||||||
.filter((p) => gameSublist.value.includes(pkgKey(p)))
|
for (const cat of pkg.rmt?.categories ?? []) {
|
||||||
.filter(
|
res.add(cat);
|
||||||
(p) =>
|
}
|
||||||
props.search === undefined ||
|
|
||||||
p.name.toLowerCase().includes(props.search.toLowerCase()) ||
|
|
||||||
p.namespace
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(props.search.toLowerCase())
|
|
||||||
),
|
|
||||||
({ namespace }) => namespace
|
|
||||||
);
|
|
||||||
if (!('local' in grouped)) {
|
|
||||||
grouped['local'] = [];
|
|
||||||
}
|
}
|
||||||
const res: [string, Package[]][] = [];
|
return [...res.values()].sort((a, b) => a.localeCompare(b));
|
||||||
|
});
|
||||||
|
|
||||||
|
const local = computed(() => {
|
||||||
|
return pkgs.allLocal
|
||||||
|
.filter((p) => gameSublist.value.includes(pkgKey(p)))
|
||||||
|
.filter((p) => p.namespace === 'local');
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupedList = computed(() => {
|
||||||
|
const searchedPkgs = pkgs.allLocal
|
||||||
|
.filter((p) => gameSublist.value.includes(pkgKey(p)))
|
||||||
|
.filter((p) => p.namespace !== 'local')
|
||||||
|
.filter(
|
||||||
|
(p) =>
|
||||||
|
props.search === undefined ||
|
||||||
|
p.name.toLowerCase().includes(props.search.toLowerCase()) ||
|
||||||
|
p.namespace.toLowerCase().includes(props.search.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
let grouped;
|
||||||
|
if (client.pkgListMode === 'namespace') {
|
||||||
|
grouped = Object.groupBy(searchedPkgs, ({ namespace }) => namespace);
|
||||||
|
} else if (client.pkgListMode === 'type') {
|
||||||
|
grouped = {
|
||||||
|
standard: [] as Package[],
|
||||||
|
native: [] as Package[],
|
||||||
|
segatools: [] as Package[],
|
||||||
|
unsupported: [] as Package[] | undefined,
|
||||||
|
};
|
||||||
|
grouped.unsupported = [];
|
||||||
|
for (const pkg of searchedPkgs) {
|
||||||
|
const loc = pkg.loc;
|
||||||
|
if (!loc || !loc.status || typeof loc.status === 'string') {
|
||||||
|
grouped.unsupported.push(pkg);
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
loc.status.OK[0] &
|
||||||
|
(Feature.GameDLL | Feature.Mempatcher | Feature.AmdDLL)
|
||||||
|
) {
|
||||||
|
grouped.native.push(pkg);
|
||||||
|
} else if (loc.status.OK[0] & Feature.Mod) {
|
||||||
|
grouped.standard.push(pkg);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
loc.status.OK[0] &
|
||||||
|
(Feature.AMNet |
|
||||||
|
Feature.Aime |
|
||||||
|
Feature.ChuniIO |
|
||||||
|
Feature.ChusanHook |
|
||||||
|
Feature.Mu3IO |
|
||||||
|
Feature.Mu3Hook)
|
||||||
|
) {
|
||||||
|
grouped.segatools.push(pkg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (grouped.unsupported.length === 0) {
|
||||||
|
delete grouped.unsupported;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
grouped = {} as { [key: string]: Package[] };
|
||||||
|
for (const pkg of searchedPkgs) {
|
||||||
|
for (const cat of pkg.rmt?.categories ?? []) {
|
||||||
|
if (client.hiddenCategories.includes(cat)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!(cat in grouped)) {
|
||||||
|
grouped[cat] = [] as Package[];
|
||||||
|
}
|
||||||
|
grouped[cat].push(pkg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let res: [string, Package[]][] = [];
|
||||||
for (const [k, v] of Object.entries(grouped)) {
|
for (const [k, v] of Object.entries(grouped)) {
|
||||||
if (v !== undefined) {
|
if (v !== undefined) {
|
||||||
res.push([k, v]);
|
res.push([k, v]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.sort((a, b) => {
|
|
||||||
return a[0] === 'local' ? -1000000 : `${a[0]}`.localeCompare(`${b[0]}`);
|
if (
|
||||||
});
|
client.pkgListMode === 'namespace' ||
|
||||||
|
client.pkgListMode === 'category'
|
||||||
|
) {
|
||||||
|
res.sort((a, b) => `${a[0]}`.localeCompare(`${b[0]}`));
|
||||||
|
} else if (client.pkgListMode === 'type') {
|
||||||
|
for (const entry of res) {
|
||||||
|
entry[0] = t(`pkglist.${entry[0]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
@ -195,7 +270,43 @@ const gameModelChunithm = gameModel('chunithm');
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Fieldset :legend="t('store.missing')" v-if="(missing?.length ?? 0) > 0">
|
<div class="flex flex-row">
|
||||||
|
<SelectButton
|
||||||
|
:options="[
|
||||||
|
{ title: t('pkglist.namespace'), value: 'namespace' },
|
||||||
|
{ title: t('pkglist.type'), value: 'type' },
|
||||||
|
{ title: t('pkglist.category'), value: 'category' },
|
||||||
|
]"
|
||||||
|
v-model="client.pkgListMode"
|
||||||
|
v-on:update:model-value="
|
||||||
|
client.save();
|
||||||
|
emit('reload-icons');
|
||||||
|
"
|
||||||
|
:allow-empty="false"
|
||||||
|
option-label="title"
|
||||||
|
option-value="value"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="grow text-right mr-2 self-center"
|
||||||
|
v-if="client.pkgListMode === 'category'"
|
||||||
|
>
|
||||||
|
{{ t('pkglist.exclusions') }}
|
||||||
|
</div>
|
||||||
|
<MultiSelect
|
||||||
|
v-if="client.pkgListMode === 'category'"
|
||||||
|
style="width: 30%"
|
||||||
|
:showToggleAll="false"
|
||||||
|
v-model="client.hiddenCategories"
|
||||||
|
v-on:value-change="
|
||||||
|
client.save();
|
||||||
|
emit('reload-icons');
|
||||||
|
"
|
||||||
|
:options="allCategories"
|
||||||
|
class="w-full grow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Fieldset :legend="t('pkglist.missing')" v-if="(missing?.length ?? 0) > 0">
|
||||||
<div class="flex items-center" v-for="p in missing">
|
<div class="flex items-center" v-for="p in missing">
|
||||||
<ModTitlecard
|
<ModTitlecard
|
||||||
show-namespace
|
show-namespace
|
||||||
@ -218,20 +329,21 @@ const gameModelChunithm = gameModel('chunithm');
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
<Fieldset v-for="[namespace, pkgs] in group" :legend="namespace">
|
<Fieldset :legend="t('pkglist.local')">
|
||||||
|
<ModListEntry v-for="p in local" :pkg="p" />
|
||||||
|
<Button
|
||||||
|
rounded
|
||||||
|
icon="pi pi-plus"
|
||||||
|
severity="success"
|
||||||
|
aria-label="install"
|
||||||
|
size="small"
|
||||||
|
class="self-center"
|
||||||
|
style="width: 2rem; height: 2rem"
|
||||||
|
v-on:click="() => (dialogVisible = true)"
|
||||||
|
/>
|
||||||
|
</Fieldset>
|
||||||
|
<Fieldset v-for="[namespace, pkgs] in groupedList" :legend="namespace">
|
||||||
<ModListEntry v-for="p in pkgs" :pkg="p" />
|
<ModListEntry v-for="p in pkgs" :pkg="p" />
|
||||||
<div v-if="namespace === 'local'">
|
|
||||||
<Button
|
|
||||||
rounded
|
|
||||||
icon="pi pi-plus"
|
|
||||||
severity="success"
|
|
||||||
aria-label="install"
|
|
||||||
size="small"
|
|
||||||
class="self-center"
|
|
||||||
style="width: 2rem; height: 2rem"
|
|
||||||
v-on:click="() => (dialogVisible = true)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import LinkButton from './LinkButton.vue';
|
|||||||
import ModTitlecard from './ModTitlecard.vue';
|
import ModTitlecard from './ModTitlecard.vue';
|
||||||
import UpdateButton from './UpdateButton.vue';
|
import UpdateButton from './UpdateButton.vue';
|
||||||
import { invoke } from '../invoke';
|
import { invoke } from '../invoke';
|
||||||
import { usePkgStore, usePrfStore } from '../stores';
|
import { useClientStore, usePkgStore, usePrfStore } from '../stores';
|
||||||
import { Feature, Package } from '../types';
|
import { Feature, Package } from '../types';
|
||||||
import { hasFeature } from '../util';
|
import { hasFeature } from '../util';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
@ -16,6 +16,7 @@ const { t } = useI18n();
|
|||||||
|
|
||||||
const prf = usePrfStore();
|
const prf = usePrfStore();
|
||||||
const pkgs = usePkgStore();
|
const pkgs = usePkgStore();
|
||||||
|
const client = useClientStore();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
pkg: Object as () => Package,
|
pkg: Object as () => Package,
|
||||||
@ -39,7 +40,13 @@ if (unsupported.value === true && model.value === true) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<ModTitlecard show-version show-icon show-description :pkg="pkg" />
|
<ModTitlecard
|
||||||
|
show-version
|
||||||
|
show-icon
|
||||||
|
show-description
|
||||||
|
:show-namespace="client.pkgListMode !== 'namespace'"
|
||||||
|
:pkg="pkg"
|
||||||
|
/>
|
||||||
<UpdateButton :pkg="pkg" />
|
<UpdateButton :pkg="pkg" />
|
||||||
<span v-tooltip="unsupported && t('store.incompatible')">
|
<span v-tooltip="unsupported && t('store.incompatible')">
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
|
@ -2,9 +2,13 @@
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import Chip from 'primevue/chip';
|
import Chip from 'primevue/chip';
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||||
|
import { listen } from '@tauri-apps/api/event';
|
||||||
import { invoke } from '../invoke';
|
import { invoke } from '../invoke';
|
||||||
import { Feature, Package } from '../types';
|
import { Feature, Package } from '../types';
|
||||||
import { hasFeature, needsUpdate } from '../util';
|
import { hasFeature, needsUpdate } from '../util';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
pkg: Object as () => Package,
|
pkg: Object as () => Package,
|
||||||
@ -17,7 +21,7 @@ const props = defineProps({
|
|||||||
|
|
||||||
const icon = ref('/no-icon.png');
|
const icon = ref('/no-icon.png');
|
||||||
|
|
||||||
(async () => {
|
const reloadIcons = async () => {
|
||||||
const src = props.pkg?.loc?.icon ?? props.pkg?.rmt?.icon;
|
const src = props.pkg?.loc?.icon ?? props.pkg?.rmt?.icon;
|
||||||
|
|
||||||
if (src === undefined) {
|
if (src === undefined) {
|
||||||
@ -32,7 +36,11 @@ const icon = ref('/no-icon.png');
|
|||||||
icon.value = '/no-icon.png';
|
icon.value = '/no-icon.png';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
};
|
||||||
|
|
||||||
|
reloadIcons();
|
||||||
|
|
||||||
|
listen('reload-icons', reloadIcons);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -97,7 +105,12 @@ const icon = ref('/no-icon.png');
|
|||||||
v-if="showNamespace && pkg?.namespace"
|
v-if="showNamespace && pkg?.namespace"
|
||||||
class="text-sm opacity-75"
|
class="text-sm opacity-75"
|
||||||
>
|
>
|
||||||
by {{ pkg.namespace }}
|
{{
|
||||||
|
t('by', { namespace: pkg.namespace }).replaceAll(
|
||||||
|
' ',
|
||||||
|
' '
|
||||||
|
)
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span class="m-2">
|
<span class="m-2">
|
||||||
<span
|
<span
|
||||||
|
@ -8,6 +8,7 @@ export default {
|
|||||||
next: 'Next',
|
next: 'Next',
|
||||||
skip: 'Skip',
|
skip: 'Skip',
|
||||||
close: 'Close',
|
close: 'Close',
|
||||||
|
by: 'by {namespace}',
|
||||||
start: {
|
start: {
|
||||||
failed: 'Start check failed',
|
failed: 'Start check failed',
|
||||||
accept: 'Run anyway',
|
accept: 'Run anyway',
|
||||||
@ -65,10 +66,22 @@ export default {
|
|||||||
deprecated: 'Show deprecated',
|
deprecated: 'Show deprecated',
|
||||||
nsfw: 'Show NSFW',
|
nsfw: 'Show NSFW',
|
||||||
incompatible: 'This package is currently incompatible with STARTLINER.',
|
incompatible: 'This package is currently incompatible with STARTLINER.',
|
||||||
missing: 'Missing',
|
|
||||||
includeCategories: 'Include categories',
|
includeCategories: 'Include categories',
|
||||||
excludeCategories: 'Exclude categories',
|
excludeCategories: 'Exclude categories',
|
||||||
},
|
},
|
||||||
|
pkglist: {
|
||||||
|
missing: 'Missing',
|
||||||
|
local: 'Local packages',
|
||||||
|
namespace: 'By namespace',
|
||||||
|
type: 'By type',
|
||||||
|
category: 'By category',
|
||||||
|
standard: 'Standard mods',
|
||||||
|
native: 'Native mods',
|
||||||
|
segatools: 'segatools',
|
||||||
|
unsupported: 'Unsupported',
|
||||||
|
exclusions: 'Exclusions:',
|
||||||
|
},
|
||||||
patch: {
|
patch: {
|
||||||
loading: 'Loading...',
|
loading: 'Loading...',
|
||||||
noneFound:
|
noneFound:
|
||||||
|
@ -8,6 +8,7 @@ export default {
|
|||||||
next: 'Dalej',
|
next: 'Dalej',
|
||||||
skip: 'Pomiń',
|
skip: 'Pomiń',
|
||||||
close: 'Zamknij',
|
close: 'Zamknij',
|
||||||
|
by: 'od {namespace}',
|
||||||
start: {
|
start: {
|
||||||
failed: 'Uruchomienie nie powiodło się',
|
failed: 'Uruchomienie nie powiodło się',
|
||||||
accept: 'Uruchom mimo to',
|
accept: 'Uruchom mimo to',
|
||||||
@ -66,10 +67,21 @@ export default {
|
|||||||
nsfw: 'Pokaż mityczny O.N.G.E.K.I. Sex Mod dlaczego ta opcja w ogóle tu jest',
|
nsfw: 'Pokaż mityczny O.N.G.E.K.I. Sex Mod dlaczego ta opcja w ogóle tu jest',
|
||||||
incompatible:
|
incompatible:
|
||||||
'Ten pakiet jest obecnie niekompatybilny ze STARTLINEREM.',
|
'Ten pakiet jest obecnie niekompatybilny ze STARTLINEREM.',
|
||||||
missing: 'Niedostępne',
|
|
||||||
includeCategories: 'Włącz kategorie',
|
includeCategories: 'Włącz kategorie',
|
||||||
excludeCategories: 'Wyłącz kategorie',
|
excludeCategories: 'Wyłącz kategorie',
|
||||||
},
|
},
|
||||||
|
pkglist: {
|
||||||
|
missing: 'Niedostępne',
|
||||||
|
local: 'Lokalne pakiety',
|
||||||
|
namespace: 'Po przestrzeni nazw',
|
||||||
|
type: 'Po typie',
|
||||||
|
category: 'Po kategorii',
|
||||||
|
standard: 'Standardowe mody',
|
||||||
|
native: 'Natywne mody',
|
||||||
|
segatools: 'segatools',
|
||||||
|
unsupported: 'Niewspierane',
|
||||||
|
exclusions: 'Czarna lista:',
|
||||||
|
},
|
||||||
patch: {
|
patch: {
|
||||||
loading: 'Wczytuję...',
|
loading: 'Wczytuję...',
|
||||||
noneFound:
|
noneFound:
|
||||||
|
@ -375,6 +375,9 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
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('users');
|
const currentTab: Ref<string> = ref('users');
|
||||||
|
const pkgListMode: Ref<'namespace' | 'type' | 'category'> =
|
||||||
|
ref('namespace');
|
||||||
|
const hiddenCategories: 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;
|
||||||
@ -445,6 +448,14 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
if (input.currentTab) {
|
if (input.currentTab) {
|
||||||
currentTab.value = input.currentTab;
|
currentTab.value = input.currentTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.pkgListMode) {
|
||||||
|
pkgListMode.value = input.pkgListMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.hiddenCategories) {
|
||||||
|
hiddenCategories.value = input.hiddenCategories;
|
||||||
|
}
|
||||||
await setLocale(locale.value);
|
await setLocale(locale.value);
|
||||||
await setTheme(theme.value);
|
await setTheme(theme.value);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -484,6 +495,8 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
onboarded: onboarded.value,
|
onboarded: onboarded.value,
|
||||||
locale: locale.value,
|
locale: locale.value,
|
||||||
currentTab: currentTab.value,
|
currentTab: currentTab.value,
|
||||||
|
pkgListMode: pkgListMode.value,
|
||||||
|
hiddenCategories: hiddenCategories.value,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -560,6 +573,8 @@ export const useClientStore = defineStore('client', () => {
|
|||||||
timeout,
|
timeout,
|
||||||
scaleModel,
|
scaleModel,
|
||||||
currentTab,
|
currentTab,
|
||||||
|
pkgListMode,
|
||||||
|
hiddenCategories,
|
||||||
_scaleValue,
|
_scaleValue,
|
||||||
scaleValue,
|
scaleValue,
|
||||||
load,
|
load,
|
||||||
|
Reference in New Issue
Block a user