Merge branch 'master' into romm-2153

This commit is contained in:
Georges-Antoine Assi
2025-09-03 09:00:01 -04:00
36 changed files with 186 additions and 321 deletions

View File

@@ -113,7 +113,7 @@ system:
ps4: ps4
psp: psp
psvita: psvita
psx: ps
psx: psx
pv1000: casio-pv-1000
samcoupe: sam-coupe
satellaview: satellaview

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import storeConsole from "@/stores/console";
import storeLanguage from "@/stores/language";
import { useIdle } from "@vueuse/core";
import { useIdle, useLocalStorage } from "@vueuse/core";
import { storeToRefs } from "pinia";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
@@ -11,10 +11,10 @@ const languageStore = storeLanguage();
const consoleStore = storeConsole();
const { consoleMode } = storeToRefs(consoleStore);
const { defaultLanguage, languages } = storeToRefs(languageStore);
const localeStorage = useLocalStorage("settings.locale", "");
const selectedLanguage = ref(
languages.value.find(
(lang) => lang.value === localStorage.getItem("settings.locale"),
) || defaultLanguage.value,
languages.value.find((lang) => lang.value === localeStorage.value) ||
defaultLanguage.value,
);
locale.value = selectedLanguage.value.value;
languageStore.setLanguage(selectedLanguage.value);

View File

@@ -118,7 +118,7 @@ function onCardClick(save: SaveSchema, event: MouseEvent) {
'border-selected': selectedSaves.some((s) => s.id === save.id),
}"
:elevation="isHovering ? 20 : 3"
@click="(e) => onCardClick(save, e)"
@click="(e: MouseEvent) => onCardClick(save, e)"
>
<v-card-text class="pa-2">
<v-row no-gutters>

View File

@@ -3,7 +3,8 @@ import CollectionCard from "@/components/common/Collection/Card.vue";
import RSection from "@/components/common/RSection.vue";
import { type CollectionType } from "@/stores/collections";
import { views } from "@/utils";
import { isNull, throttle } from "lodash";
import { useLocalStorage } from "@vueuse/core";
import { throttle } from "lodash";
import { onBeforeUnmount, onMounted, ref } from "vue";
const props = defineProps<{
@@ -15,24 +16,14 @@ const props = defineProps<{
| "gridSmartCollections";
}>();
const storedCollections = localStorage.getItem(`settings.${props.setting}`);
const gridCollections = ref(
isNull(storedCollections) ? false : storedCollections === "true",
);
const storedEnable3DEffect = localStorage.getItem("settings.enable3DEffect");
const enable3DEffect = ref(
isNull(storedEnable3DEffect) ? false : storedEnable3DEffect === "true",
);
const gridCollections = useLocalStorage(`settings.${props.setting}`, false);
const enable3DEffect = useLocalStorage("settings.enable3DEffect", false);
const visibleCollections = ref(72);
const isHovering = ref(false);
const hoveringCollectionId = ref();
function toggleGridCollections() {
gridCollections.value = !gridCollections.value;
localStorage.setItem(
`settings.${props.setting}`,
gridCollections.value.toString(),
);
}
function onHover(emitData: { isHovering: boolean; id: number }) {

View File

@@ -3,7 +3,7 @@ import GameCard from "@/components/common/Game/Card/Base.vue";
import RSection from "@/components/common/RSection.vue";
import storeRoms from "@/stores/roms";
import { views } from "@/utils";
import { isNull } from "lodash";
import { useLocalStorage } from "@vueuse/core";
import { storeToRefs } from "pinia";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
@@ -11,16 +11,11 @@ import { useI18n } from "vue-i18n";
const { t } = useI18n();
const romsStore = storeRoms();
const { continuePlayingRoms } = storeToRefs(romsStore);
const storedContinuePlaying = localStorage.getItem(
const gridContinuePlayingRoms = useLocalStorage(
"settings.gridContinuePlayingRoms",
false,
);
const gridContinuePlayingRoms = ref(
isNull(storedContinuePlaying) ? false : storedContinuePlaying === "true",
);
const storedEnable3DEffect = localStorage.getItem("settings.enable3DEffect");
const enable3DEffect = ref(
isNull(storedEnable3DEffect) ? false : storedEnable3DEffect === "true",
);
const enable3DEffect = useLocalStorage("settings.enable3DEffect", false);
const isHovering = ref(false);
const hoveringRomId = ref();
const openedMenu = ref(false);
@@ -28,10 +23,6 @@ const openedMenuRomId = ref();
function toggleGridContinuePlaying() {
gridContinuePlayingRoms.value = !gridContinuePlayingRoms.value;
localStorage.setItem(
"settings.gridContinuePlayingRoms",
gridContinuePlayingRoms.value.toString(),
);
}
function onHover(emitData: { isHovering: boolean; id: number }) {

View File

@@ -3,7 +3,7 @@ import PlatformCard from "@/components/common/Platform/Card.vue";
import RSection from "@/components/common/RSection.vue";
import storePlatforms from "@/stores/platforms";
import { views } from "@/utils";
import { isNull } from "lodash";
import { useLocalStorage } from "@vueuse/core";
import { storeToRefs } from "pinia";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
@@ -11,23 +11,13 @@ import { useI18n } from "vue-i18n";
const { t } = useI18n();
const platformsStore = storePlatforms();
const { filledPlatforms } = storeToRefs(platformsStore);
const storedPlatforms = localStorage.getItem("settings.gridPlatforms");
const gridPlatforms = ref(
isNull(storedPlatforms) ? false : storedPlatforms === "true",
);
const storedEnable3DEffect = localStorage.getItem("settings.enable3DEffect");
const enable3DEffect = ref(
isNull(storedEnable3DEffect) ? false : storedEnable3DEffect === "true",
);
const gridPlatforms = useLocalStorage("settings.gridPlatforms", false);
const enable3DEffect = useLocalStorage("settings.enable3DEffect", false);
const isHovering = ref(false);
const hoveringPlatformId = ref();
function toggleGridPlatforms() {
gridPlatforms.value = !gridPlatforms.value;
localStorage.setItem(
"settings.gridPlatforms",
gridPlatforms.value.toString(),
);
}
function onHover(emitData: { isHovering: boolean; id: number }) {

View File

@@ -2,15 +2,11 @@
import Skeleton from "@/components/common/Game/Card/Skeleton.vue";
import RSection from "@/components/common/RSection.vue";
import { views } from "@/utils";
import { isNull } from "lodash";
import { ref } from "vue";
import { useLocalStorage } from "@vueuse/core";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const storedPlatforms = localStorage.getItem("settings.gridPlatforms");
const gridPlatforms = ref(
isNull(storedPlatforms) ? false : storedPlatforms === "true",
);
const gridPlatforms = useLocalStorage("settings.gridPlatforms", false);
const PLATFORM_SKELETON_COUNT = 12;
</script>

View File

@@ -3,7 +3,7 @@ import GameCard from "@/components/common/Game/Card/Base.vue";
import RSection from "@/components/common/RSection.vue";
import storeRoms from "@/stores/roms";
import { views } from "@/utils";
import { isNull } from "lodash";
import { useLocalStorage } from "@vueuse/core";
import { storeToRefs } from "pinia";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
@@ -11,14 +11,8 @@ import { useI18n } from "vue-i18n";
const { t } = useI18n();
const romsStore = storeRoms();
const { recentRoms } = storeToRefs(romsStore);
const storedGridRecentRoms = localStorage.getItem("settings.gridRecentRoms");
const gridRecentRoms = ref(
isNull(storedGridRecentRoms) ? false : storedGridRecentRoms === "true",
);
const storedEnable3DEffect = localStorage.getItem("settings.enable3DEffect");
const enable3DEffect = ref(
isNull(storedEnable3DEffect) ? false : storedEnable3DEffect === "true",
);
const gridRecentRoms = useLocalStorage("settings.gridRecentRoms", false);
const enable3DEffect = useLocalStorage("settings.enable3DEffect", false);
const isHovering = ref(false);
const hoveringRomId = ref();
const openedMenu = ref(false);
@@ -26,10 +20,6 @@ const openedMenuRomId = ref();
function toggleGridRecentRoms() {
gridRecentRoms.value = !gridRecentRoms.value;
localStorage.setItem(
"settings.gridRecentRoms",
gridRecentRoms.value.toString(),
);
}
function onHover(emitData: { isHovering: boolean; id: number }) {

View File

@@ -2,8 +2,8 @@
import InterfaceOption from "@/components/Settings/UserInterface/InterfaceOption.vue";
import RSection from "@/components/common/RSection.vue";
import storeCollections from "@/stores/collections";
import { isNull } from "lodash";
import { computed, ref } from "vue";
import { useLocalStorage } from "@vueuse/core";
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { useDisplay } from "vuetify";
@@ -11,89 +11,40 @@ const { t } = useI18n();
const { smAndDown } = useDisplay();
const collectionsStore = storeCollections();
// Initialize refs from localStorage
// Home
const storedShowStats = localStorage.getItem("settings.showStats");
const showStatsRef = ref(
isNull(storedShowStats) ? true : storedShowStats === "true",
);
const storedShowRecentRoms = localStorage.getItem("settings.showRecentRoms");
const showRecentRomsRef = ref(
isNull(storedShowRecentRoms) ? true : storedShowRecentRoms === "true",
);
const storedShowContinuePlaying = localStorage.getItem(
const showStatsRef = useLocalStorage("settings.showStats", true);
const showRecentRomsRef = useLocalStorage("settings.showRecentRoms", true);
const showContinuePlayingRef = useLocalStorage(
"settings.showContinuePlaying",
true,
);
const showContinuePlayingRef = ref(
isNull(storedShowContinuePlaying)
? true
: storedShowContinuePlaying === "true",
);
const storedShowPlatforms = localStorage.getItem("settings.showPlatforms");
const showPlatformsRef = ref(
isNull(storedShowPlatforms) ? true : storedShowPlatforms === "true",
);
const storedShowCollections = localStorage.getItem("settings.showCollections");
const showCollectionsRef = ref(
isNull(storedShowCollections) ? true : storedShowCollections === "true",
);
const showPlatformsRef = useLocalStorage("settings.showPlatforms", true);
const showCollectionsRef = useLocalStorage("settings.showCollections", true);
// Virtual collections
const storedShowVirtualCollections = localStorage.getItem(
const showVirtualCollectionsRef = useLocalStorage(
"settings.showVirtualCollections",
true,
);
const showVirtualCollectionsRef = ref(
isNull(storedShowVirtualCollections)
? true
: storedShowVirtualCollections === "true",
);
const storedVirtualCollectionType = localStorage.getItem(
const virtualCollectionTypeRef = useLocalStorage(
"settings.virtualCollectionType",
);
const virtualCollectionTypeRef = ref(
isNull(storedVirtualCollectionType)
? "collection"
: storedVirtualCollectionType,
"collection",
);
// Platforms drawer
const storedPlatformsGroupBy = localStorage.getItem(
const platformsGroupByRef = useLocalStorage<string | null>(
"settings.platformsGroupBy",
);
const platformsGroupByRef = ref(
isNull(storedPlatformsGroupBy) || storedPlatformsGroupBy === "null"
? null
: storedPlatformsGroupBy,
null,
);
// Gallery
const storedGroupRoms = localStorage.getItem("settings.groupRoms");
const groupRomsRef = ref(
isNull(storedGroupRoms) ? true : storedGroupRoms === "true",
);
const storedSiblings = localStorage.getItem("settings.showSiblings");
const siblingsRef = ref(
isNull(storedSiblings) ? true : storedSiblings === "true",
);
const storedRegions = localStorage.getItem("settings.showRegions");
const regionsRef = ref(isNull(storedRegions) ? true : storedRegions === "true");
const storedLanguages = localStorage.getItem("settings.showLanguages");
const languagesRef = ref(
isNull(storedLanguages) ? true : storedLanguages === "true",
);
const storedStatus = localStorage.getItem("settings.showStatus");
const statusRef = ref(isNull(storedStatus) ? true : storedStatus === "true");
const storedActionBar = localStorage.getItem("settings.showActionBar");
const actionBarRef = ref(
isNull(storedActionBar) ? false : storedActionBar === "true",
);
const stored3DEffect = localStorage.getItem("settings.enable3DEffect");
const enable3DEffectRef = ref(
isNull(stored3DEffect) ? false : stored3DEffect === "true",
);
const groupRomsRef = useLocalStorage("settings.groupRoms", true);
const siblingsRef = useLocalStorage("settings.showSiblings", true);
const regionsRef = useLocalStorage("settings.showRegions", true);
const languagesRef = useLocalStorage("settings.showLanguages", true);
const statusRef = useLocalStorage("settings.showStatus", true);
const actionBarRef = useLocalStorage("settings.showActionBar", false);
const enable3DEffectRef = useLocalStorage("settings.enable3DEffect", false);
const homeOptions = computed(() => [
{
@@ -211,72 +162,57 @@ const galleryOptions = computed(() => [
const setPlatformDrawerGroupBy = (value: string) => {
platformsGroupByRef.value = value;
localStorage.setItem("settings.platformsGroupBy", value);
};
const toggleShowContinuePlaying = (value: boolean) => {
showContinuePlayingRef.value = value;
localStorage.setItem("settings.showContinuePlaying", value.toString());
};
const toggleShowPlatforms = (value: boolean) => {
showPlatformsRef.value = value;
localStorage.setItem("settings.showPlatforms", value.toString());
};
const toggleShowCollections = (value: boolean) => {
showCollectionsRef.value = value;
localStorage.setItem("settings.showCollections", value.toString());
};
const toggleShowVirtualCollections = (value: boolean) => {
showVirtualCollectionsRef.value = value;
localStorage.setItem("settings.showVirtualCollections", value.toString());
};
const setVirtualCollectionType = async (value: string) => {
virtualCollectionTypeRef.value = value;
localStorage.setItem("settings.virtualCollectionType", value);
collectionsStore.fetchVirtualCollections(value);
};
const toggleShowStats = (value: boolean) => {
showStatsRef.value = value;
localStorage.setItem("settings.showStats", value.toString());
};
const toggleShowRecentRoms = (value: boolean) => {
showRecentRomsRef.value = value;
localStorage.setItem("settings.showRecentRoms", value.toString());
};
const toggleGroupRoms = (value: boolean) => {
groupRomsRef.value = value;
localStorage.setItem("settings.groupRoms", value.toString());
};
const toggleSiblings = (value: boolean) => {
siblingsRef.value = value;
localStorage.setItem("settings.showSiblings", value.toString());
};
const toggleRegions = (value: boolean) => {
regionsRef.value = value;
localStorage.setItem("settings.showRegions", value.toString());
};
const toggleLanguages = (value: boolean) => {
languagesRef.value = value;
localStorage.setItem("settings.showLanguages", value.toString());
};
const toggleStatus = (value: boolean) => {
statusRef.value = value;
localStorage.setItem("settings.showStatus", value.toString());
};
const toggleActionBar = (value: boolean) => {
actionBarRef.value = value;
localStorage.setItem("settings.showActionBar", value.toString());
};
const toggle3DEffect = (value: boolean) => {
enable3DEffectRef.value = value;
localStorage.setItem("settings.enable3DEffect", value.toString());
};
</script>
<template>

View File

@@ -1,16 +1,18 @@
<script setup lang="ts">
import storeLanguage from "@/stores/language";
import { useLocalStorage } from "@vueuse/core";
import { storeToRefs } from "pinia";
import { ref } from "vue";
import { useI18n } from "vue-i18n";
const { locale } = useI18n();
const languageStore = storeLanguage();
const { languages, selectedLanguage } = storeToRefs(languageStore);
const localeStorage = useLocalStorage("settings.locale", "");
function changeLanguage() {
locale.value = selectedLanguage.value.value;
localStorage.setItem("settings.locale", selectedLanguage.value.value);
localeStorage.value = selectedLanguage.value.value;
}
</script>
<template>

View File

@@ -3,14 +3,14 @@ import ThemeOption from "@/components/Settings/UserInterface/ThemeOption.vue";
import RSection from "@/components/common/RSection.vue";
import { autoThemeKey, themes } from "@/styles/themes";
import { isKeyof } from "@/types";
import { computed, ref } from "vue";
import { useLocalStorage } from "@vueuse/core";
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { useTheme } from "vuetify";
const { t } = useI18n();
const theme = useTheme();
const storedTheme = parseInt(localStorage.getItem("settings.theme") ?? "");
const selectedTheme = ref(isNaN(storedTheme) ? autoThemeKey : storedTheme);
const selectedTheme = useLocalStorage("settings.theme", autoThemeKey);
const themeOptions = computed(() => [
{
name: "dark",
@@ -27,7 +27,6 @@ const themeOptions = computed(() => [
]);
function toggleTheme() {
localStorage.setItem("settings.theme", selectedTheme.value.toString());
const mediaMatch = window.matchMedia("(prefers-color-scheme: dark)");
if (selectedTheme.value === autoThemeKey) {
theme.global.name.value = mediaMatch.matches ? "dark" : "light";

View File

@@ -15,7 +15,7 @@ import storeRoms from "@/stores/roms";
import { type SimpleRom } from "@/stores/roms";
import type { Events } from "@/types/emitter";
import { getMissingCoverImage, getUnmatchedCoverImage } from "@/utils/covers";
import { isNull } from "lodash";
import { useLocalStorage } from "@vueuse/core";
import type { Emitter } from "mitt";
import VanillaTilt from "vanilla-tilt";
import { computed, ref, onMounted, onBeforeUnmount, inject } from "vue";
@@ -110,15 +110,8 @@ const fallbackCoverImage = computed(() =>
);
const activeMenu = ref(false);
const showActionBarAlways = isNull(
localStorage.getItem("settings.showActionBar"),
)
? false
: localStorage.getItem("settings.showActionBar") === "true";
const showSiblings = isNull(localStorage.getItem("settings.showSiblings"))
? true
: localStorage.getItem("settings.showSiblings") === "true";
const showActionBarAlways = useLocalStorage("settings.showActionBar", false);
const showSiblings = useLocalStorage("settings.showSiblings", true);
const hasNotes = computed(() => {
if (!romsStore.isSimpleRom(props.rom)) return false;

View File

@@ -6,19 +6,14 @@ import {
getEmojiForStatus,
getTextForStatus,
} from "@/utils";
import { identity, isNull } from "lodash";
import { useLocalStorage } from "@vueuse/core";
import { identity } from "lodash";
import { computed } from "vue";
const props = defineProps<{ rom: SimpleRom }>();
const showRegions = isNull(localStorage.getItem("settings.showRegions"))
? true
: localStorage.getItem("settings.showRegions") === "true";
const showLanguages = isNull(localStorage.getItem("settings.showLanguages"))
? true
: localStorage.getItem("settings.showLanguages") === "true";
const showStatus = isNull(localStorage.getItem("settings.showStatus"))
? true
: localStorage.getItem("settings.showStatus") === "true";
const showRegions = useLocalStorage("settings.showRegions", true);
const showLanguages = useLocalStorage("settings.showLanguages", true);
const showStatus = useLocalStorage("settings.showStatus", true);
const playingStatus = computed(() => {
const { now_playing, backlogged, status } = props.rom?.rom_user ?? {};

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import storeGalleryView from "@/stores/galleryView";
import storePlatforms from "@/stores/platforms";
import { isNull } from "lodash";
import { useLocalStorage } from "@vueuse/core";
import { computed } from "vue";
import { useDisplay } from "vuetify";
@@ -22,11 +22,7 @@ const { smAndDown } = useDisplay();
const platformsStore = storePlatforms();
const galleryViewStore = storeGalleryView();
const showActionBarAlways = isNull(
localStorage.getItem("settings.showActionBar"),
)
? false
: localStorage.getItem("settings.showActionBar") === "true";
const showActionBarAlways = useLocalStorage("settings.showActionBar", false);
const showActionBar = computed(() => smAndDown.value || showActionBarAlways);

View File

@@ -19,7 +19,7 @@ import {
languageToEmoji,
regionToEmoji,
} from "@/utils";
import { isNull } from "lodash";
import { useLocalStorage } from "@vueuse/core";
import { storeToRefs } from "pinia";
import { computed } from "vue";
import { useRouter } from "vue-router";
@@ -32,9 +32,7 @@ withDefaults(
showPlatformIcon: false,
},
);
const showSiblings = isNull(localStorage.getItem("settings.showSiblings"))
? true
: localStorage.getItem("settings.showSiblings") === "true";
const showSiblings = useLocalStorage("settings.showSiblings", true);
const router = useRouter();
const downloadStore = storeDownload();
const romsStore = storeRoms();

View File

@@ -5,7 +5,7 @@ import CollectionListItem from "@/components/common/Collection/ListItem.vue";
import storeCollections from "@/stores/collections";
import storeNavigation from "@/stores/navigation";
import type { Events } from "@/types/emitter";
import { isNull } from "lodash";
import { useLocalStorage } from "@vueuse/core";
import type { Emitter } from "mitt";
import { storeToRefs } from "pinia";
import { inject, onBeforeUnmount, onMounted, ref, watch, computed } from "vue";
@@ -27,11 +27,10 @@ const emitter = inject<Emitter<Events>>("emitter");
const visibleVirtualCollections = ref(72);
const tabIndex = computed(() => (activeCollectionsDrawer.value ? 0 : -1));
const showVirtualCollections = isNull(
localStorage.getItem("settings.showVirtualCollections"),
)
? true
: localStorage.getItem("settings.showVirtualCollections") === "true";
const showVirtualCollections = useLocalStorage(
"settings.showVirtualCollections",
true,
);
async function addCollection() {
emitter?.emit("showCreateCollectionDialog", null);

View File

@@ -12,6 +12,7 @@ import SettingsDrawer from "@/components/common/Navigation/SettingsDrawer.vue";
import UploadBtn from "@/components/common/Navigation/UploadBtn.vue";
import UserBtn from "@/components/common/Navigation/UserBtn.vue";
import storeNavigation from "@/stores/navigation";
import { useLocalStorage } from "@vueuse/core";
import { storeToRefs } from "pinia";
import { useDisplay } from "vuetify";
@@ -19,12 +20,14 @@ const { smAndDown } = useDisplay();
const navigationStore = storeNavigation();
const { mainBarCollapsed } = storeToRefs(navigationStore);
const mainBarCollapsedStorage = useLocalStorage(
"settings.mainBarCollapsed",
false,
);
function collapse() {
mainBarCollapsed.value = !mainBarCollapsed.value;
localStorage.setItem(
"settings.mainBarCollapsed",
mainBarCollapsed.value.toString(),
);
mainBarCollapsedStorage.value = mainBarCollapsed.value;
}
</script>
<template>

View File

@@ -3,34 +3,28 @@ import PlatformListItem from "@/components/common/Platform/ListItem.vue";
import storeNavigation from "@/stores/navigation";
import type { Platform } from "@/stores/platforms";
import storePlatforms from "@/stores/platforms";
import { useLocalStorage } from "@vueuse/core";
import { storeToRefs } from "pinia";
import { ref, watch, computed } from "vue";
import { useI18n } from "vue-i18n";
import { useDisplay } from "vuetify";
type GroupByType = "family_name" | "generation" | "category" | null;
const { t } = useI18n();
const { mdAndUp, smAndDown } = useDisplay();
const navigationStore = storeNavigation();
const platformsStore = storePlatforms();
const { filteredPlatforms, filterText } = storeToRefs(platformsStore);
const { activePlatformsDrawer } = storeToRefs(navigationStore);
const ALLOWED_GROUP_BY = ["family_name", "generation", "category"] as const;
type GroupByType = (typeof ALLOWED_GROUP_BY)[number] | null;
const textFieldRef = ref();
const triggerElement = ref<HTMLElement | null>(null);
const openPanels = ref<number[]>([]);
const initializeGroupBy = (): GroupByType => {
const stored = localStorage.getItem("settings.platformsGroupBy");
return stored && ALLOWED_GROUP_BY.includes(stored as any)
? (stored as GroupByType)
: null;
};
const groupBy = ref<GroupByType>(initializeGroupBy());
const groupBy = useLocalStorage<GroupByType | null>(
"settings.platformsGroupBy",
null,
);
const tabIndex = computed(() => (activePlatformsDrawer.value ? 0 : -1));

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import storeHeartbeat from "@/stores/heartbeat";
import { useLocalStorage } from "@vueuse/core";
import semver from "semver";
import { onMounted, ref } from "vue";
@@ -7,9 +8,10 @@ const heartbeat = storeHeartbeat();
const { VERSION } = heartbeat.value.SYSTEM;
const GITHUB_VERSION = ref(VERSION);
const latestVersionDismissed = ref(VERSION === "development");
const dismissedVersion = useLocalStorage("dismissedVersion", "");
function dismissVersionBanner() {
localStorage.setItem("dismissedVersion", GITHUB_VERSION.value);
dismissedVersion.value = GITHUB_VERSION.value;
latestVersionDismissed.value = true;
}
@@ -26,7 +28,7 @@ async function fetchLatestVersion() {
// Hide if the version is not valid
!semver.valid(VERSION) ||
// Hide if the version is the same as the dismissed version
json.tag_name === localStorage.getItem("dismissedVersion") ||
json.tag_name === dismissedVersion.value ||
// Hide if the version is less than 2 hours old
publishedAt.getTime() + 2 * 60 * 60 * 1000 > Date.now();
} catch (error) {

View File

@@ -2,7 +2,6 @@
import { InputBus, InputBusSymbol } from "@/console/input/bus";
import { attachGamepad } from "@/console/input/gamepad";
import { attachKeyboard } from "@/console/input/keyboard";
import { initializeSfx } from "@/console/utils/sfx";
import { ROUTES } from "@/plugins/router";
import { useConsoleTheme } from "@/stores/consoleTheme";
import { useIdle } from "@vueuse/core";
@@ -54,7 +53,6 @@ let detachGamepad: (() => void) | null = null;
onMounted(() => {
themeStore.initializeTheme();
initializeSfx();
// Establish a root input scope so child views can subscribe safely
bus.pushScope();

View File

@@ -1,21 +1,14 @@
import type { InputAction } from "../input/actions";
import { useLocalStorage } from "@vueuse/core";
let sfxEnabled = true;
const sfxEnabled = useLocalStorage("console-sfx-enabled", true);
export function setSfxEnabled(enabled: boolean): void {
sfxEnabled = enabled;
localStorage.setItem("console-sfx-enabled", enabled ? "true" : "false");
sfxEnabled.value = enabled;
}
export function getSfxEnabled(): boolean {
return sfxEnabled;
}
export function initializeSfx(): void {
const saved = localStorage.getItem("console-sfx-enabled");
if (saved !== null) {
sfxEnabled = saved === "true";
}
return sfxEnabled.value;
}
let ctx: AudioContext | null = null;
@@ -130,7 +123,7 @@ function playClick(opts: ClickOpts = {}) {
}
export function playSfx(kind: SfxType) {
if (!sfxEnabled) return;
if (!sfxEnabled.value) return;
// Lazy resume (required on some browsers until user gesture)
ensureCtx()

View File

@@ -327,7 +327,7 @@ const currentStateId = computed(
() => rom.value?.user_states?.[selectedStateIndex.value]?.id,
);
function relativeTime(date: string | Date) {
function formatRelativeDate(date: string | Date) {
return formatDistanceToNow(new Date(date), { addSuffix: true });
}
@@ -624,7 +624,7 @@ onUnmounted(() => {
selectedStateIndex === i,
}"
:aria-label="
'State from ' + relativeTime(st.updated_at)
'State from ' + formatRelativeDate(st.updated_at)
"
@click="startWithState(i)"
>
@@ -649,7 +649,7 @@ onUnmounted(() => {
class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-black/0 px-2 pt-4 pb-1 text-[10px] text-white/80 tracking-wide flex justify-between items-end"
>
<span class="truncate max-w-[90%]">{{
relativeTime(st.updated_at)
formatRelativeDate(st.updated_at)
}}</span>
</div>
</button>

View File

@@ -15,10 +15,28 @@ import {
areThreadsRequiredForEJSCore,
getDownloadPath,
} from "@/utils";
import { useLocalStorage } from "@vueuse/core";
import { storeToRefs } from "pinia";
import { onMounted, onBeforeUnmount, ref, watch, nextTick } from "vue";
import { useRoute, useRouter } from "vue-router";
const createPlayerStorage = (romId: number, platformSlug: string) => ({
initialSaveId: useLocalStorage(
`player:${romId}:initial_save_id`,
null as string | null,
),
initialStateId: useLocalStorage(
`player:${romId}:initial_state_id`,
null as string | null,
),
disc: useLocalStorage(`player:${romId}:disc`, null as string | null),
core: useLocalStorage(`player:${platformSlug}:core`, null as string | null),
biosId: useLocalStorage(
`player:${platformSlug}:bios_id`,
null as string | null,
),
});
const route = useRoute();
const router = useRouter();
const { getBezelImagePath } = useThemeAssets();
@@ -299,6 +317,9 @@ async function boot() {
const { data: rom } = await romApi.getRom({ romId });
romRef.value = rom;
// Create player storage instances
const playerStorage = createPlayerStorage(rom.id, rom.platform_slug);
const selectedInitialSave = initialSaveId
? rom.user_saves?.find((s) => s.id === initialSaveId)
: null;
@@ -312,9 +333,10 @@ async function boot() {
// Configure EmulatorJS globals
const supported = getSupportedEJSCores(rom.platform_slug);
const storedCore = localStorage.getItem(`player:${rom.platform_slug}:core`);
const core =
storedCore && supported.includes(storedCore) ? storedCore : supported[0];
playerStorage.core.value && supported.includes(playerStorage.core.value)
? playerStorage.core.value
: supported[0];
window.EJS_core = core;
window.EJS_controlScheme = getControlSchemeForPlatform(rom.platform_slug);
@@ -323,21 +345,16 @@ async function boot() {
if (initialSaveId) {
// Persist chosen save ID for later logic
localStorage.setItem(
`player:${rom.id}:initial_save_id`,
String(initialSaveId),
);
playerStorage.initialSaveId.value = String(initialSaveId);
}
if (initialStateId) {
localStorage.setItem(
`player:${rom.id}:initial_state_id`,
String(initialStateId),
);
playerStorage.initialStateId.value = String(initialStateId);
}
// Disc selection persistence
const storedDisc = localStorage.getItem(`player:${rom.id}:disc`);
const discId = storedDisc ? parseInt(storedDisc) : null;
const discId = playerStorage.disc.value
? parseInt(playerStorage.disc.value)
: null;
window.EJS_gameUrl = getDownloadPath({
rom: rom,
fileIDs: discId ? [discId] : [],
@@ -348,11 +365,8 @@ async function boot() {
const { data: firmware } = await firmwareApi.getFirmware({
platformId: rom.platform_id,
});
const storedBiosID = localStorage.getItem(
`player:${rom.platform_slug}:bios_id`,
);
const bios = storedBiosID
? firmware.find((f) => f.id === parseInt(storedBiosID))
const bios = playerStorage.biosId.value
? firmware.find((f) => f.id === parseInt(playerStorage.biosId.value!))
: null;
window.EJS_biosUrl = bios

View File

@@ -22,7 +22,7 @@ import storeCollections from "@/stores/collections";
import storeNavigation from "@/stores/navigation";
import storePlatforms from "@/stores/platforms";
import type { Events } from "@/types/emitter";
import { isNull } from "lodash";
import { useLocalStorage } from "@vueuse/core";
import type { Emitter } from "mitt";
import { inject, onBeforeMount, ref } from "vue";
@@ -35,19 +35,13 @@ emitter?.on("refreshDrawer", async () => {
platformsStore.fetchPlatforms();
});
const showVirtualCollections = isNull(
localStorage.getItem("settings.showVirtualCollections"),
)
? true
: localStorage.getItem("settings.showVirtualCollections") === "true";
const storedVirtualCollectionType = localStorage.getItem(
"settings.virtualCollectionType",
const showVirtualCollections = useLocalStorage(
"settings.showVirtualCollections",
true,
);
const virtualCollectionTypeRef = ref(
isNull(storedVirtualCollectionType)
? "collection"
: storedVirtualCollectionType,
const virtualCollectionTypeRef = useLocalStorage(
"settings.virtualCollectionType",
"collection",
);
function unhackNavbar() {

View File

@@ -1,6 +1,7 @@
import { themes, dark, light, autoThemeKey } from "@/styles/themes";
import { isKeyof } from "@/types";
import "@mdi/font/css/materialdesignicons.css";
import { useLocalStorage } from "@vueuse/core";
import { createVuetify } from "vuetify";
import "vuetify/styles";
@@ -10,14 +11,13 @@ mediaMatch.addEventListener("change", (event) => {
});
function getTheme() {
const storedTheme = parseInt(localStorage.getItem("settings.theme") ?? "");
const storedTheme = useLocalStorage("settings.theme", autoThemeKey);
if (
!isNaN(storedTheme) &&
storedTheme !== autoThemeKey &&
isKeyof(storedTheme, themes)
storedTheme.value !== autoThemeKey &&
isKeyof(storedTheme.value, themes)
) {
return themes[storedTheme];
return themes[storedTheme.value];
}
return mediaMatch.matches ? "dark" : "light";

View File

@@ -1,25 +1,22 @@
import { resolveAsset, clearAssetCache } from "@/console/utils/assetResolver";
import { useLocalStorage } from "@vueuse/core";
import { defineStore } from "pinia";
import { ref, computed } from "vue";
import { computed } from "vue";
export const useConsoleTheme = defineStore("consoleTheme", () => {
const themeName = ref<string>("default");
const themeName = useLocalStorage("console-theme", "default");
const availableThemes = ["default", "neon"];
function setTheme(newThemeName: string): void {
clearAssetCache();
themeName.value = newThemeName;
localStorage.setItem("console-theme", newThemeName);
updateThemeCSS();
updateBackgroundCSS();
}
function initializeTheme(): void {
const savedTheme = localStorage.getItem("console-theme") || "default";
themeName.value = savedTheme;
updateThemeCSS();
updateBackgroundCSS();
}

View File

@@ -1,7 +1,10 @@
import { useLocalStorage } from "@vueuse/core";
import { defineStore } from "pinia";
const currentViewStorage = useLocalStorage("currentView", 0);
const defaultGalleryState = {
currentView: JSON.parse(localStorage.getItem("currentView") ?? "0") as number,
currentView: currentViewStorage.value,
defaultAspectRatioCover: 2 / 3,
defaultAspectRatioCollection: 2 / 3,
defaultAspectRatioScreenshot: 16 / 9,
@@ -16,7 +19,7 @@ export default defineStore("galleryView", {
actions: {
setView(view: number) {
this.currentView = view;
localStorage.setItem("currentView", this.currentView.toString());
currentViewStorage.value = view;
},
switchActiveFirmwareDrawer() {
this.activeFirmwareDrawer = !this.activeFirmwareDrawer;

View File

@@ -1,16 +1,16 @@
import { ROUTES } from "@/plugins/router";
import { isNull } from "lodash";
import { useLocalStorage } from "@vueuse/core";
import { defineStore } from "pinia";
const mainBarCollapsed = useLocalStorage("settings.mainBarCollapsed", false);
const defaultNavigationState = {
activePlatformsDrawer: false,
activeCollectionsDrawer: false,
activeSettingsDrawer: false,
activePlatformInfoDrawer: false,
activeCollectionInfoDrawer: false,
mainBarCollapsed: isNull(localStorage.getItem("settings.mainBarCollapsed"))
? false
: localStorage.getItem("settings.mainBarCollapsed") === "true",
mainBarCollapsed: mainBarCollapsed.value,
};
export default defineStore("navigation", {

View File

@@ -13,11 +13,12 @@ import storeGalleryView from "@/stores/galleryView";
import storeRoms, { type SimpleRom } from "@/stores/roms";
import type { Events } from "@/types/emitter";
import { views } from "@/utils";
import { isNull, throttle } from "lodash";
import { useLocalStorage } from "@vueuse/core";
import { throttle } from "lodash";
import type { Emitter } from "mitt";
import { storeToRefs } from "pinia";
import { inject, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { onBeforeRouteUpdate, useRoute, useRouter } from "vue-router";
import { onBeforeRouteUpdate, useRoute } from "vue-router";
const props = defineProps<{
collections: CollectionType[];
@@ -38,16 +39,12 @@ const {
fetchTotalRoms,
} = storeToRefs(romsStore);
const noCollectionError = ref(false);
const router = useRouter();
const emitter = inject<Emitter<Events>>("emitter");
const isHovering = ref(false);
const hoveringRomId = ref();
const openedMenu = ref(false);
const openedMenuRomId = ref();
const storedEnable3DEffect = localStorage.getItem("settings.enable3DEffect");
const enable3DEffect = ref(
isNull(storedEnable3DEffect) ? false : storedEnable3DEffect === "true",
);
const enable3DEffect = useLocalStorage("settings.enable3DEffect", false);
let timeout: ReturnType<typeof setTimeout>;
async function fetchRoms() {

View File

@@ -13,7 +13,8 @@ import storePlatforms from "@/stores/platforms";
import storeRoms, { type SimpleRom } from "@/stores/roms";
import type { Events } from "@/types/emitter";
import { views } from "@/utils";
import { isNull, throttle } from "lodash";
import { useLocalStorage } from "@vueuse/core";
import { throttle } from "lodash";
import type { Emitter } from "mitt";
import { storeToRefs } from "pinia";
import { inject, onBeforeUnmount, onMounted, ref, watch } from "vue";
@@ -41,10 +42,7 @@ const isHovering = ref(false);
const hoveringRomId = ref();
const openedMenu = ref(false);
const openedMenuRomId = ref();
const storedEnable3DEffect = localStorage.getItem("settings.enable3DEffect");
const enable3DEffect = ref(
isNull(storedEnable3DEffect) ? false : storedEnable3DEffect === "true",
);
const enable3DEffect = useLocalStorage("settings.enable3DEffect", false);
let timeout: ReturnType<typeof setTimeout>;
async function fetchRoms() {

View File

@@ -12,7 +12,8 @@ import storeGalleryView from "@/stores/galleryView";
import storeRoms, { type SimpleRom } from "@/stores/roms";
import type { Events } from "@/types/emitter";
import { views } from "@/utils";
import { isNull, throttle } from "lodash";
import { useLocalStorage } from "@vueuse/core";
import { throttle } from "lodash";
import type { Emitter } from "mitt";
import { storeToRefs } from "pinia";
import { inject, onBeforeUnmount, onMounted, ref } from "vue";
@@ -36,10 +37,7 @@ const isHovering = ref(false);
const hoveringRomId = ref();
const openedMenu = ref(false);
const openedMenuRomId = ref();
const storedEnable3DEffect = localStorage.getItem("settings.enable3DEffect");
const enable3DEffect = ref(
isNull(storedEnable3DEffect) ? false : storedEnable3DEffect === "true",
);
const enable3DEffect = useLocalStorage("settings.enable3DEffect", false);
let timeout: ReturnType<typeof setTimeout>;
function onHover(emitData: { isHovering: boolean; id: number }) {

View File

@@ -10,6 +10,7 @@ import Stats from "@/components/Home/Stats.vue";
import storeCollections from "@/stores/collections";
import storePlatforms from "@/stores/platforms";
import storeRoms from "@/stores/roms";
import { useLocalStorage } from "@vueuse/core";
import { storeToRefs } from "pinia";
import { onBeforeMount, ref, computed } from "vue";
import { useI18n } from "vue-i18n";
@@ -29,18 +30,22 @@ const {
fetchingVirtualCollections,
} = storeToRefs(collectionsStore);
function getSettingValue(key: string, defaultValue: boolean = true): boolean {
const stored = localStorage.getItem(`settings.${key}`);
return stored === null ? defaultValue : stored === "true";
}
const showStats = getSettingValue("showStats");
const showRecentRoms = getSettingValue("showRecentRoms");
const showContinuePlaying = getSettingValue("showContinuePlaying");
const showPlatforms = getSettingValue("showPlatforms");
const showCollections = getSettingValue("showCollections");
const showVirtualCollections = getSettingValue("showVirtualCollections");
const showSmartCollections = getSettingValue("showSmartCollections");
const showStats = useLocalStorage("settings.showStats", true);
const showRecentRoms = useLocalStorage("settings.showRecentRoms", true);
const showContinuePlaying = useLocalStorage(
"settings.showContinuePlaying",
true,
);
const showPlatforms = useLocalStorage("settings.showPlatforms", true);
const showCollections = useLocalStorage("settings.showCollections", true);
const showVirtualCollections = useLocalStorage(
"settings.showVirtualCollections",
true,
);
const showSmartCollections = useLocalStorage(
"settings.showSmartCollections",
true,
);
const fetchingRecentAdded = ref(false);
const fetchingContinuePlaying = ref(false);

View File

@@ -14,8 +14,8 @@ import { formatTimestamp, getSupportedEJSCores } from "@/utils";
import { getEmptyCoverImage } from "@/utils/covers";
import CacheDialog from "@/views/Player/EmulatorJS/CacheDialog.vue";
import Player from "@/views/Player/EmulatorJS/Player.vue";
import { useLocalStorage } from "@vueuse/core";
import { formatDistanceToNow } from "date-fns";
import { isNull } from "lodash";
import { storeToRefs } from "pinia";
import { onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
@@ -42,8 +42,7 @@ const selectedCore = ref<string | null>(null);
const selectedDisc = ref<number | null>(null);
const supportedCores = ref<string[]>([]);
const gameRunning = ref(false);
const storedFSOP = localStorage.getItem("fullScreenOnPlay");
const fullScreenOnPlay = ref(isNull(storedFSOP) ? true : storedFSOP === "true");
const fullScreenOnPlay = useLocalStorage("fullScreenOnPlay", true);
function onPlay() {
if (rom.value && auth.scopes.includes("roms.user.write")) {
@@ -90,7 +89,6 @@ function onPlay() {
function onFullScreenChange() {
fullScreenOnPlay.value = !fullScreenOnPlay.value;
localStorage.setItem("fullScreenOnPlay", fullScreenOnPlay.value.toString());
}
async function onlyQuit() {

View File

@@ -21,6 +21,7 @@ import {
getControlSchemeForPlatform,
getDownloadPath,
} from "@/utils";
import { useLocalStorage } from "@vueuse/core";
import type { Emitter } from "mitt";
import { storeToRefs } from "pinia";
import { inject, onBeforeUnmount, onMounted, ref } from "vue";

View File

@@ -4,7 +4,7 @@ import { ROUTES } from "@/plugins/router";
import romApi from "@/services/api/rom";
import type { DetailedRom } from "@/stores/roms";
import { getDownloadPath } from "@/utils";
import { isNull } from "lodash";
import { useLocalStorage } from "@vueuse/core";
import { nextTick, onMounted, ref } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute } from "vue-router";
@@ -16,8 +16,7 @@ const { t } = useI18n();
const route = useRoute();
const rom = ref<DetailedRom | null>(null);
const gameRunning = ref(false);
const storedFSOP = localStorage.getItem("fullScreenOnPlay");
const fullScreenOnPlay = ref(isNull(storedFSOP) ? true : storedFSOP === "true");
const fullScreenOnPlay = useLocalStorage("fullScreenOnPlay", true);
const backgroundColor = ref(DEFAULT_BACKGROUND_COLOR);
declare global {
@@ -57,7 +56,6 @@ function onPlay() {
function onFullScreenChange() {
fullScreenOnPlay.value = !fullScreenOnPlay.value;
localStorage.setItem("fullScreenOnPlay", fullScreenOnPlay.value.toString());
}
function onBackgroundColorChange() {

View File

@@ -8,6 +8,7 @@ import socket from "@/services/socket";
import storeHeartbeat, { type MetadataOption } from "@/stores/heartbeat";
import storePlatforms from "@/stores/platforms";
import storeScanning from "@/stores/scanning";
import { useLocalStorage } from "@vueuse/core";
import { storeToRefs } from "pinia";
import { computed, ref, watch, type DefineComponent } from "vue";
import { useI18n } from "vue-i18n";
@@ -29,12 +30,10 @@ const expansionPanels = ref<DefineComponent | null>(null);
useAutoScroll(scanLog, expansionPanels);
const metadataOptions = computed(() => heartbeat.getAllMetadataOptions());
const storedMetadataSources = computed<string[]>(() => {
const storedSources = localStorage.getItem(
LOCAL_STORAGE_METADATA_SOURCES_KEY,
);
return storedSources ? JSON.parse(storedSources) : [];
});
const storedMetadataSources = useLocalStorage(
LOCAL_STORAGE_METADATA_SOURCES_KEY,
[] as string[],
);
const metadataSources = ref<MetadataOption[]>(
metadataOptions.value.filter((m) =>
storedMetadataSources.value.includes(m.value),
@@ -100,10 +99,7 @@ async function scan() {
if (!socket.connected) socket.connect();
// Store selected meta sources in storage
localStorage.setItem(
LOCAL_STORAGE_METADATA_SOURCES_KEY,
JSON.stringify(metadataSources.value.map((s) => s.value)),
);
storedMetadataSources.value = metadataSources.value.map((s) => s.value);
socket.emit("scan", {
platforms: platformsToScan.value,