diff --git a/apps/app-frontend/src/App.vue b/apps/app-frontend/src/App.vue index 760c8d47e..caed5e872 100644 --- a/apps/app-frontend/src/App.vue +++ b/apps/app-frontend/src/App.vue @@ -3,6 +3,7 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { RouterView, useRoute, useRouter } from 'vue-router' import { ArrowBigUpDashIcon, + ChangeSkinIcon, CompassIcon, DownloadIcon, HomeIcon, @@ -399,6 +400,9 @@ function handleAuxClick(e) { > + + + -import { ref } from 'vue' +import { ref, useTemplateRef } from 'vue' import { NewModal as Modal } from '@modrinth/ui' import { show_ads_window, hide_ads_window } from '@/helpers/ads.js' import { useTheming } from '@/store/theme.ts' @@ -26,16 +26,16 @@ const props = defineProps({ default: true, }, }) -const modal = ref(null) +const modal = useTemplateRef('modal') defineExpose({ - show: () => { + show: (e: MouseEvent) => { hide_ads_window() - modal.value.show() + modal.value?.show(e) }, hide: () => { onModalHide() - modal.value.hide() + modal.value?.hide() }, }) diff --git a/apps/app-frontend/src/components/ui/skin/CapeButton.vue b/apps/app-frontend/src/components/ui/skin/CapeButton.vue new file mode 100644 index 000000000..b05b4e3b7 --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin/CapeButton.vue @@ -0,0 +1,85 @@ + + + + diff --git a/apps/app-frontend/src/components/ui/skin/EditSkinModal.vue b/apps/app-frontend/src/components/ui/skin/EditSkinModal.vue new file mode 100644 index 000000000..6c65d2285 --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin/EditSkinModal.vue @@ -0,0 +1,33 @@ + + diff --git a/apps/app-frontend/src/components/ui/skin/SelectCapeModal.vue b/apps/app-frontend/src/components/ui/skin/SelectCapeModal.vue new file mode 100644 index 000000000..ae750773c --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin/SelectCapeModal.vue @@ -0,0 +1,77 @@ + + diff --git a/apps/app-frontend/src/components/ui/skin/SkinButton.vue b/apps/app-frontend/src/components/ui/skin/SkinButton.vue new file mode 100644 index 000000000..13c162272 --- /dev/null +++ b/apps/app-frontend/src/components/ui/skin/SkinButton.vue @@ -0,0 +1,181 @@ + + + + + diff --git a/apps/app-frontend/src/helpers/skins.ts b/apps/app-frontend/src/helpers/skins.ts new file mode 100644 index 000000000..ded98c531 --- /dev/null +++ b/apps/app-frontend/src/helpers/skins.ts @@ -0,0 +1,64 @@ +import { invoke } from '@tauri-apps/api/core' + +export interface Cape { + id: string + name: string + texture: string + is_default: boolean + is_equipped: boolean +} + +export type SkinModel = 'Classic' | 'Slim' | 'Unknown' +export type SkinSource = 'Default' | 'CustomExternal' | 'Custom' + +export interface Skin { + texture_key: string + name?: string + variant: SkinModel + cape_id?: string + texture: string + source: SkinSource + is_equipped: boolean +} + +export async function get_available_capes(): Promise { + return await invoke('plugin:minecraft-skins|get_available_capes', {}) +} + +export async function get_available_skins(): Promise { + return await invoke('plugin:minecraft-skins|get_available_skins', {}) +} + +export async function add_and_equip_custom_skin( + texture_blob: Uint8Array, + variant: SkinModel, + cape_override?: Cape, +): Promise { + await invoke('plugin:minecraft-skins|add_and_equip_custom_skin', { + texture_blob, + variant, + cape_override, + }) +} + +export async function set_default_cape(cape?: Cape): Promise { + await invoke('plugin:minecraft-skins|set_default_cape', { + cape, + }) +} + +export async function equip_skin(skin: Skin): Promise { + await invoke('plugin:minecraft-skins|equip_skin', { + skin, + }) +} + +export async function remove_custom_skin(skin: Skin): Promise { + await invoke('plugin:minecraft-skins|remove_custom_skin', { + skin, + }) +} + +export async function unequip_skin(): Promise { + await invoke('plugin:minecraft-skins|unequip_skin') +} diff --git a/apps/app-frontend/src/pages/Index.vue b/apps/app-frontend/src/pages/Index.vue index e5c1e0689..14776c2bb 100644 --- a/apps/app-frontend/src/pages/Index.vue +++ b/apps/app-frontend/src/pages/Index.vue @@ -10,6 +10,7 @@ import dayjs from 'dayjs' import { get_search_results } from '@/helpers/cache.js' import type { SearchResult } from '@modrinth/utils' import RecentWorldsList from '@/components/ui/world/RecentWorldsList.vue' +import type { GameInstance } from '@/helpers/types' const route = useRoute() const breadcrumbs = useBreadcrumbs() @@ -82,7 +83,7 @@ async function refreshFeaturedProjects() { await fetchInstances() await refreshFeaturedProjects() -const unlistenProfile = await profile_listener(async (e) => { +const unlistenProfile = await profile_listener(async (e: { event: string; profile_path_id: string }) => { await fetchInstances() if (e.event === 'added' || e.event === 'created' || e.event === 'removed') { @@ -97,8 +98,8 @@ onUnmounted(() => {