mirror of
https://github.com/KevinMidboe/seasoned.git
synced 2026-04-24 08:43:36 +00:00
Minor improvements and code cleanup across components
- Refactor PlexSettings component for better organization - Add utility functions to utils.ts for shared logic - Update API methods for improved error handling - Clean up navigation icon components - Remove unused code from CommandPalette and SeasonedButton - Fix minor issues in Movie popup component - Update page component imports (RegisterPage, TorrentsPage)
This commit is contained in:
16
src/api.ts
16
src/api.ts
@@ -413,6 +413,20 @@ const unlinkPlexAccount = async () => {
|
||||
});
|
||||
};
|
||||
|
||||
const plexRecentlyAddedInLibrary = async (id: number) => {
|
||||
const url = new URL(`/api/v2/plex/recently_added/${id}`, API_HOSTNAME);
|
||||
const options: RequestInit = {
|
||||
credentials: "include"
|
||||
};
|
||||
|
||||
return fetch(url.href, options)
|
||||
.then(resp => resp.json())
|
||||
.catch(error => {
|
||||
console.error(`api error fetch plex recently added`); // eslint-disable-line no-console
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
||||
// - - - User graphs - - -
|
||||
|
||||
const fetchGraphData = async (
|
||||
@@ -543,6 +557,7 @@ const elasticSearchMoviesAndShows = async (query: string, count = 22) => {
|
||||
};
|
||||
|
||||
export {
|
||||
API_HOSTNAME,
|
||||
getMovie,
|
||||
getShow,
|
||||
getPerson,
|
||||
@@ -559,6 +574,7 @@ export {
|
||||
getRequestStatus,
|
||||
linkPlexAccount,
|
||||
unlinkPlexAccount,
|
||||
plexRecentlyAddedInLibrary,
|
||||
register,
|
||||
login,
|
||||
logout,
|
||||
|
||||
@@ -42,8 +42,7 @@
|
||||
.navigation-link {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
height: var(--header-size);
|
||||
width: var(--header-size);
|
||||
min-height: var(--header-size);
|
||||
list-style: none;
|
||||
padding: 1rem 0.15rem;
|
||||
text-align: center;
|
||||
|
||||
@@ -90,18 +90,10 @@
|
||||
|
||||
@include desktop {
|
||||
grid-template-rows: var(--header-size);
|
||||
grid-auto-flow: row;
|
||||
}
|
||||
|
||||
@include mobile {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.navigation-icons > *:last-child) {
|
||||
margin-top: auto;
|
||||
justify-self: end;
|
||||
align-self: end;
|
||||
background-color: red;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -215,7 +215,8 @@
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const ASSET_URL = "https://image.tmdb.org/t/p/";
|
||||
const COLORS_URL = "https://colors.schleppe.cloud/colors";
|
||||
// const COLORS_URL = "https://colors.schleppe.cloud/colors";
|
||||
const COLORS_URL = "http://localhost:8080/colors";
|
||||
const ASSET_SIZES = ["w500", "w780", "original"];
|
||||
|
||||
const media: Ref<IMovie | IShow> = ref();
|
||||
@@ -435,7 +436,7 @@
|
||||
|
||||
> img {
|
||||
width: 100%;
|
||||
border-radius: inherit;
|
||||
border-radius: calc(1.6rem - 1px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="plex-settings">
|
||||
<!-- Unconnected state -->
|
||||
<PlexAuthButton
|
||||
v-if="!isPlexConnected"
|
||||
v-if="!showPlexInformation"
|
||||
@auth-success="handleAuthSuccess"
|
||||
@auth-error="handleAuthError"
|
||||
/>
|
||||
@@ -16,20 +16,20 @@
|
||||
/>
|
||||
|
||||
<PlexLibraryStats
|
||||
:movies="libraryStats.movies"
|
||||
:shows="libraryStats.shows"
|
||||
:music="libraryStats.music"
|
||||
:watchtime="libraryStats.watchtime"
|
||||
:loading="loadingLibraries"
|
||||
:movies="libraryStats?.movies"
|
||||
:shows="libraryStats?.['tv shows']"
|
||||
:music="libraryStats?.music"
|
||||
:watchtime="libraryStats?.watchtime || 0"
|
||||
:loading="syncingLibrary"
|
||||
@open-library="showLibraryDetails"
|
||||
/>
|
||||
|
||||
<PlexServerInfo
|
||||
:serverName="plexServer"
|
||||
:lastSync="lastSync"
|
||||
:syncing="syncing"
|
||||
:syncing="syncingServer"
|
||||
@sync="syncLibrary"
|
||||
@unlink="confirmUnlink"
|
||||
@unlink="() => (showUnlinkModal = true)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -38,16 +38,18 @@
|
||||
|
||||
<!-- Unlink Confirmation Modal -->
|
||||
<PlexUnlinkModal
|
||||
v-if="showConfirmModal"
|
||||
v-if="showUnlinkModal"
|
||||
@confirm="unauthenticatePlex"
|
||||
@cancel="cancelUnlink"
|
||||
@cancel="() => (showUnlinkModal = false)"
|
||||
/>
|
||||
|
||||
<!-- Library Details Modal -->
|
||||
<PlexLibraryModal
|
||||
v-if="showLibraryModal && selectedLibrary"
|
||||
:libraryType="selectedLibrary"
|
||||
:details="libraryDetails[selectedLibrary]"
|
||||
:details="libraryStats[selectedLibrary]"
|
||||
:serverUrl="plexServerUrl"
|
||||
:serverMachineId="plexMachineId"
|
||||
@close="closeLibraryModal"
|
||||
/>
|
||||
</div>
|
||||
@@ -55,7 +57,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
import SeasonedMessages from "@/components/ui/SeasonedMessages.vue";
|
||||
import PlexAuthButton from "@/components/plex/PlexAuthButton.vue";
|
||||
import PlexProfileCard from "@/components/plex/PlexProfileCard.vue";
|
||||
@@ -64,184 +65,167 @@
|
||||
import PlexUnlinkModal from "@/components/plex/PlexUnlinkModal.vue";
|
||||
import PlexLibraryModal from "@/components/plex/PlexLibraryModal.vue";
|
||||
import { usePlexAuth } from "@/composables/usePlexAuth";
|
||||
import { usePlexApi } from "@/composables/usePlexApi";
|
||||
import { usePlexLibraries } from "@/composables/usePlexLibraries";
|
||||
import {
|
||||
fetchPlexServers,
|
||||
fetchPlexUserData,
|
||||
fetchLibraryDetails
|
||||
} from "@/composables/usePlexApi";
|
||||
import type { Ref } from "vue";
|
||||
import { linkPlexAccount, unlinkPlexAccount } from "../../api";
|
||||
import { ErrorMessageTypes } from "../../interfaces/IErrorMessage";
|
||||
import type { IErrorMessage } from "../../interfaces/IErrorMessage";
|
||||
|
||||
const messages: Ref<IErrorMessage[]> = ref([]);
|
||||
const loading = ref(false);
|
||||
const syncing = ref(false);
|
||||
const showConfirmModal = ref(false);
|
||||
const syncingServer = ref(false);
|
||||
const syncingLibrary = ref(false);
|
||||
const showUnlinkModal = ref(false);
|
||||
const plexUsername = ref<string>("");
|
||||
const plexUserData = ref<any>(null);
|
||||
const isPlexConnected = ref<boolean>(false);
|
||||
const showPlexInformation = ref<boolean>(false);
|
||||
const hasLocalStorageData = ref<boolean>(false);
|
||||
const hasCookieData = ref<boolean>(false);
|
||||
const showLibraryModal = ref<boolean>(false);
|
||||
const selectedLibrary = ref<string>("");
|
||||
const loadingLibraries = ref<boolean>(false);
|
||||
|
||||
const plexServer = ref("");
|
||||
const plexServerUrl = ref("");
|
||||
const plexMachineId = ref("");
|
||||
const lastSync = ref("");
|
||||
const lastSync = ref(sessionStorage.getItem("plex_library_last_sync"));
|
||||
const libraryStats = ref({
|
||||
movies: 0,
|
||||
shows: 0,
|
||||
music: 0,
|
||||
watchtime: 0
|
||||
});
|
||||
const libraryDetails = ref<any>({
|
||||
movies: {
|
||||
total: 0,
|
||||
recentlyAdded: [],
|
||||
genres: [],
|
||||
totalDuration: "0 hours"
|
||||
},
|
||||
shows: {
|
||||
total: 0,
|
||||
recentlyAdded: [],
|
||||
genres: [],
|
||||
totalEpisodes: 0,
|
||||
totalDuration: "0 hours"
|
||||
},
|
||||
music: {
|
||||
total: 0,
|
||||
recentlyAdded: [],
|
||||
genres: [],
|
||||
totalTracks: 0
|
||||
}
|
||||
});
|
||||
|
||||
const store = useStore();
|
||||
const emit = defineEmits<{
|
||||
(e: "reload"): void;
|
||||
}>();
|
||||
|
||||
// Composables
|
||||
const { getCookie, setPlexAuthCookie, cleanup } = usePlexAuth();
|
||||
const {
|
||||
fetchPlexUserData,
|
||||
fetchPlexServers,
|
||||
fetchLibrarySections,
|
||||
fetchLibraryDetails
|
||||
} = usePlexApi();
|
||||
const { loadLibraries } = usePlexLibraries();
|
||||
const { getPlexAuthCookie, setPlexAuthCookie, cleanup } = usePlexAuth();
|
||||
|
||||
// ----- Connection check -----
|
||||
function checkPlexConnection() {
|
||||
const cachedData = localStorage.getItem("plex_user_data");
|
||||
const authToken = getCookie("plex_auth_token");
|
||||
const storeHasPlexUserId = store.getters["user/plexUserId"];
|
||||
hasLocalStorageData.value = !!cachedData;
|
||||
hasCookieData.value = !!authToken;
|
||||
isPlexConnected.value = !!(cachedData || authToken || storeHasPlexUserId);
|
||||
return isPlexConnected.value;
|
||||
const authToken = getPlexAuthCookie();
|
||||
showPlexInformation.value = !!authToken;
|
||||
return showPlexInformation.value;
|
||||
}
|
||||
|
||||
// ----- Library loading -----
|
||||
async function fetchPlexLibraries(authToken: string) {
|
||||
try {
|
||||
loadingLibraries.value = true;
|
||||
const server = await fetchPlexServers(authToken);
|
||||
if (!server) {
|
||||
console.error("No Plex server found");
|
||||
return;
|
||||
}
|
||||
|
||||
plexServer.value = server.name;
|
||||
plexServerUrl.value = server.url;
|
||||
plexMachineId.value = server.machineIdentifier;
|
||||
lastSync.value = new Date().toLocaleString();
|
||||
|
||||
const sections = await fetchLibrarySections(authToken, server.url);
|
||||
if (!sections || sections.length === 0) {
|
||||
console.error("No library sections found");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await loadLibraries(
|
||||
sections,
|
||||
authToken,
|
||||
server.url,
|
||||
server.machineIdentifier,
|
||||
plexUsername.value,
|
||||
fetchLibraryDetails
|
||||
);
|
||||
|
||||
libraryStats.value = result.stats;
|
||||
libraryDetails.value = result.details;
|
||||
} catch (error) {
|
||||
console.error("[PlexSettings] Error fetching Plex libraries:", error);
|
||||
} finally {
|
||||
loadingLibraries.value = false;
|
||||
async function loadPlexServer() {
|
||||
// return cached value from sessionStorage if exists
|
||||
const cacheKey = "plex_server_data";
|
||||
const cachedData = sessionStorage.getItem(cacheKey);
|
||||
if (cachedData) {
|
||||
const server = JSON.parse(cachedData);
|
||||
plexServer.value = server?.name;
|
||||
plexServerUrl.value = server?.url;
|
||||
plexMachineId.value = server?.machineIdentifier;
|
||||
return;
|
||||
}
|
||||
|
||||
// get token from cookie
|
||||
const authToken = getPlexAuthCookie();
|
||||
if (!authToken) return;
|
||||
|
||||
// make api call for data
|
||||
syncingServer.value = true;
|
||||
const server = await fetchPlexServers(authToken);
|
||||
|
||||
if (server) {
|
||||
// set server name & id
|
||||
plexServer.value = server?.name;
|
||||
plexServerUrl.value = server?.url;
|
||||
plexMachineId.value = server?.machineIdentifier;
|
||||
// cache in sessionStorage
|
||||
sessionStorage.setItem(cacheKey, JSON.stringify(server));
|
||||
|
||||
// set last-sync date
|
||||
const now = new Date().toLocaleString();
|
||||
lastSync.value = now;
|
||||
sessionStorage.setItem("plex_library_last_sync", now);
|
||||
} else {
|
||||
console.log("unable to load plex server informmation");
|
||||
}
|
||||
|
||||
syncingServer.value = false;
|
||||
}
|
||||
|
||||
// ----- User data loading -----
|
||||
async function loadPlexUserData() {
|
||||
checkPlexConnection();
|
||||
const cachedData = localStorage.getItem("plex_user_data");
|
||||
// return cached value from sessionStorage if exists
|
||||
const cacheKey = "plex_user_data";
|
||||
const cachedData = sessionStorage.getItem(cacheKey);
|
||||
hasLocalStorageData.value = !!cachedData;
|
||||
if (cachedData) {
|
||||
try {
|
||||
plexUserData.value = JSON.parse(cachedData);
|
||||
plexUsername.value = plexUserData.value.username;
|
||||
isPlexConnected.value = true;
|
||||
} catch (error) {
|
||||
console.error("[PlexSettings] Error parsing cached Plex data:", error);
|
||||
}
|
||||
plexUserData.value = JSON.parse(cachedData);
|
||||
plexUsername.value = plexUserData.value.username;
|
||||
return;
|
||||
}
|
||||
const authToken = getCookie("plex_auth_token");
|
||||
hasCookieData.value = !!authToken;
|
||||
if (authToken) {
|
||||
const userData = await fetchPlexUserData(authToken);
|
||||
if (userData) {
|
||||
plexUserData.value = userData;
|
||||
plexUsername.value = userData.username;
|
||||
isPlexConnected.value = true;
|
||||
} else if (!cachedData) {
|
||||
isPlexConnected.value = false;
|
||||
}
|
||||
if (isPlexConnected.value) {
|
||||
await fetchPlexLibraries(authToken);
|
||||
}
|
||||
|
||||
// get token from cookie
|
||||
const authToken = getPlexAuthCookie();
|
||||
if (!authToken) return;
|
||||
|
||||
// make api call for data
|
||||
const userData = await fetchPlexUserData(authToken);
|
||||
|
||||
if (userData) {
|
||||
// set plex user data
|
||||
plexUserData.value = userData;
|
||||
plexUsername.value = userData?.username;
|
||||
|
||||
// cache in sessionStorage
|
||||
sessionStorage.setItem(cacheKey, JSON.stringify(userData));
|
||||
} else {
|
||||
isPlexConnected.value = false;
|
||||
console.log("unable to load user data from plex");
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Load plex libary details -----
|
||||
async function loadPlexLibraries() {
|
||||
// return cached value from sessionStorage if exists
|
||||
const cacheKey = "plex_library_data";
|
||||
const cachedData = sessionStorage.getItem(cacheKey);
|
||||
hasLocalStorageData.value = !!cachedData;
|
||||
if (cachedData) {
|
||||
libraryStats.value = JSON.parse(cachedData);
|
||||
return;
|
||||
}
|
||||
|
||||
// get token from cookie
|
||||
const authToken = getPlexAuthCookie();
|
||||
if (!authToken) return;
|
||||
|
||||
// make api call for data
|
||||
syncingLibrary.value = true;
|
||||
const library = await fetchLibraryDetails();
|
||||
|
||||
if (library) {
|
||||
libraryStats.value = library;
|
||||
// cache in sessionStorage
|
||||
sessionStorage.setItem(cacheKey, JSON.stringify(library));
|
||||
} else {
|
||||
console.log("unable to load plex library details");
|
||||
}
|
||||
|
||||
syncingLibrary.value = false;
|
||||
}
|
||||
|
||||
// ----- OAuth flow (handlers for PlexAuthButton events) -----
|
||||
async function handleAuthSuccess(authToken: string) {
|
||||
try {
|
||||
setPlexAuthCookie(authToken);
|
||||
const userData = await fetchPlexUserData(authToken);
|
||||
if (userData) {
|
||||
plexUserData.value = userData;
|
||||
plexUsername.value = userData.username;
|
||||
isPlexConnected.value = true;
|
||||
}
|
||||
const { success, message } = await linkPlexAccount(authToken);
|
||||
if (success) {
|
||||
emit("reload");
|
||||
await fetchPlexLibraries(authToken);
|
||||
messages.value.push({
|
||||
type: ErrorMessageTypes.Success,
|
||||
title: "Authenticated with Plex",
|
||||
message: message || "Successfully connected your Plex account"
|
||||
} as IErrorMessage);
|
||||
} else {
|
||||
messages.value.push({
|
||||
type: ErrorMessageTypes.Error,
|
||||
title: "Authentication failed",
|
||||
message: message || "Could not connect to Plex"
|
||||
} as IErrorMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[PlexSettings] Error in handleAuthSuccess:", error);
|
||||
setPlexAuthCookie(authToken);
|
||||
checkPlexConnection();
|
||||
const success = await loadAll();
|
||||
|
||||
if (success) {
|
||||
messages.value.push({
|
||||
type: ErrorMessageTypes.Success,
|
||||
title: "Authenticated with Plex",
|
||||
message: "Successfully connected your Plex account"
|
||||
} as IErrorMessage);
|
||||
} else {
|
||||
console.error("[PlexSettings] Error in handleAuthSuccess:");
|
||||
|
||||
messages.value.push({
|
||||
type: ErrorMessageTypes.Error,
|
||||
title: "Authentication failed",
|
||||
@@ -259,35 +243,24 @@
|
||||
}
|
||||
|
||||
// ----- Unlink flow -----
|
||||
function confirmUnlink() {
|
||||
showConfirmModal.value = true;
|
||||
}
|
||||
function cancelUnlink() {
|
||||
showConfirmModal.value = false;
|
||||
}
|
||||
async function unauthenticatePlex() {
|
||||
showConfirmModal.value = false;
|
||||
loading.value = true;
|
||||
const response = await unlinkPlexAccount();
|
||||
if (response?.success) {
|
||||
localStorage.removeItem("plex_user_data");
|
||||
document.cookie =
|
||||
"plex_auth_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; SameSite=Strict";
|
||||
plexUserData.value = null;
|
||||
plexUsername.value = "";
|
||||
isPlexConnected.value = false;
|
||||
emit("reload");
|
||||
}
|
||||
showUnlinkModal.value = false;
|
||||
sessionStorage.removeItem("plex_user_data");
|
||||
sessionStorage.removeItem("plex_server_data");
|
||||
sessionStorage.removeItem("plex_library_data");
|
||||
sessionStorage.removeItem("plex_library_last_sync");
|
||||
document.cookie =
|
||||
"plex_auth_token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC; SameSite=Strict";
|
||||
plexUserData.value = null;
|
||||
plexUsername.value = "";
|
||||
showPlexInformation.value = false;
|
||||
emit("reload");
|
||||
|
||||
messages.value.push({
|
||||
type: response.success
|
||||
? ErrorMessageTypes.Success
|
||||
: ErrorMessageTypes.Error,
|
||||
title: response.success
|
||||
? "Unlinked Plex account"
|
||||
: "Something went wrong",
|
||||
message: response.message
|
||||
type: ErrorMessageTypes.Success,
|
||||
title: "Unlinked Plex account",
|
||||
message: "All browser storage has been clear of plex account"
|
||||
} as IErrorMessage);
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// ----- Library modal -----
|
||||
@@ -304,39 +277,60 @@
|
||||
|
||||
// ----- Sync -----
|
||||
async function syncLibrary() {
|
||||
syncing.value = true;
|
||||
const authToken = getCookie("plex_auth_token");
|
||||
const authToken = getPlexAuthCookie();
|
||||
if (!authToken) {
|
||||
messages.value.push({
|
||||
type: ErrorMessageTypes.Error,
|
||||
title: "Sync failed",
|
||||
message: "No authentication token found"
|
||||
} as IErrorMessage);
|
||||
syncing.value = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await fetchPlexLibraries(authToken);
|
||||
|
||||
sessionStorage.removeItem("plex_user_data");
|
||||
sessionStorage.removeItem("plex_server_data");
|
||||
sessionStorage.removeItem("plex_library_data");
|
||||
|
||||
const success = await loadAll();
|
||||
|
||||
if (success) {
|
||||
messages.value.push({
|
||||
type: ErrorMessageTypes.Success,
|
||||
title: "Library synced",
|
||||
message: "Your Plex library has been successfully synced"
|
||||
} as IErrorMessage);
|
||||
} catch (error) {
|
||||
} else {
|
||||
messages.value.push({
|
||||
type: ErrorMessageTypes.Error,
|
||||
title: "Sync failed",
|
||||
message: "An error occurred while syncing your library"
|
||||
} as IErrorMessage);
|
||||
} finally {
|
||||
syncing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// ---- Helper load all ----
|
||||
async function loadAll() {
|
||||
let success = false;
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
loadPlexServer(),
|
||||
loadPlexUserData(),
|
||||
loadPlexLibraries()
|
||||
]);
|
||||
|
||||
success = true;
|
||||
} catch (error) {
|
||||
console.log("loadall error, some info might be missing");
|
||||
}
|
||||
|
||||
checkPlexConnection();
|
||||
loadPlexUserData();
|
||||
});
|
||||
return success;
|
||||
}
|
||||
|
||||
// ---- Lifecycle functions ----
|
||||
onMounted(loadAll);
|
||||
|
||||
onUnmounted(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
@@ -139,7 +139,6 @@
|
||||
import IconMovie from "@/icons/IconMovie.vue";
|
||||
import IconActivity from "@/icons/IconActivity.vue";
|
||||
import IconProfile from "@/icons/IconProfile.vue";
|
||||
import IconRequest from "@/icons/IconRequest.vue";
|
||||
import IconInbox from "@/icons/IconInbox.vue";
|
||||
import IconSearch from "@/icons/IconSearch.vue";
|
||||
import IconEdit from "@/icons/IconEdit.vue";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<button
|
||||
type="button"
|
||||
:class="{ active: active, fullwidth: fullWidth }"
|
||||
@click="emit('click')"
|
||||
@click="event => emit('click', event)"
|
||||
>
|
||||
<slot></slot>
|
||||
</button>
|
||||
@@ -15,7 +15,7 @@
|
||||
}
|
||||
|
||||
interface Emit {
|
||||
(e: "click");
|
||||
(e: "click", event?: MouseEvent);
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<seasoned-button @click="submit">Register</seasoned-button>
|
||||
</form>
|
||||
|
||||
<router-link class="link" to="/signin"
|
||||
<router-link class="link" to="/login"
|
||||
>Have a user? Sign in here</router-link
|
||||
>
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
@include mobile-only {
|
||||
font-size: 1.5rem;
|
||||
margin: 0 0 1rem 0;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
14
src/utils.ts
14
src/utils.ts
@@ -129,3 +129,17 @@ export function convertSecondsToHumanReadable(_value, values = null) {
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function formatNumber(n: number) {
|
||||
if (!n?.toString()) return n;
|
||||
|
||||
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ");
|
||||
}
|
||||
|
||||
export function formatBytes(bytes: number): string {
|
||||
if (bytes === 0) return "0 Bytes";
|
||||
const k = 1024;
|
||||
const sizes = ["Bytes", "KB", "MB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user